├── log ├── .gitkeep └── app.log.2025-02-20 ├── src ├── lib │ ├── util │ │ ├── my-tdesk.js │ │ ├── my-attheme.js │ │ ├── light-darkdem.ts │ │ ├── types.ts │ │ ├── colors.ts │ │ ├── contrast.js │ │ ├── con-tar.js │ │ ├── rgb-hsl.js │ │ ├── themes.ts │ │ ├── text-color.js │ │ └── theme-preview.ts │ ├── atthemejs │ │ ├── .prettierrc.toml │ │ ├── lib │ │ │ ├── types.js │ │ │ ├── variables.d.ts │ │ │ ├── fallbacks.d.ts │ │ │ ├── tools │ │ │ │ ├── node │ │ │ │ │ ├── fromFile.d.ts │ │ │ │ │ ├── toFile.d.ts │ │ │ │ │ ├── fromFile.js │ │ │ │ │ └── toFile.js │ │ │ │ ├── browser │ │ │ │ │ ├── fromFile.d.ts │ │ │ │ │ ├── toBlob.d.ts │ │ │ │ │ ├── fromFile.js │ │ │ │ │ └── toBlob.js │ │ │ │ ├── themeToObject.d.ts │ │ │ │ └── themeToObject.js │ │ │ ├── defaultThemes │ │ │ │ ├── graphite.d.ts │ │ │ │ ├── default.d.ts │ │ │ │ ├── dark.d.ts │ │ │ │ ├── mono.d.ts │ │ │ │ └── arctic.d.ts │ │ │ ├── types.d.ts │ │ │ ├── parseContents.d.ts │ │ │ ├── serializeTheme.d.ts │ │ │ ├── serializeTheme.js │ │ │ ├── parseContents.js │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── fallbacks.js │ │ ├── package.json │ │ ├── LICENSE │ │ └── readme.md │ ├── config │ │ └── log_config.ts │ ├── preview │ │ ├── render-pool.js │ │ ├── fallbacks.js │ │ └── bot_core.js │ ├── variables │ │ ├── helpers.ts │ │ ├── tgx-theme.ts │ │ └── tgios-theme.ts │ ├── picserver │ │ └── svgps.ts │ └── wapper │ │ ├── NAttheme.ts │ │ └── NTdesktop.ts ├── restore │ └── pubmap.ts ├── mianwrapper │ ├── index.d.ts │ ├── tool │ │ └── colorcacu.ts │ ├── desktop │ │ └── OriginImpletement │ │ │ ├── make-desktop-theme-util.ts │ │ │ ├── theme-desktop-api.ts │ │ │ └── override.ts │ ├── templete │ │ ├── desktop │ │ │ ├── black.ts │ │ │ └── white.ts │ │ ├── android │ │ │ ├── black.ts │ │ │ └── white.ts │ │ ├── base-theme-info.ts │ │ └── base-theme-operation.ts │ ├── preview │ │ └── theme-preview-api.ts │ ├── android-check.ts │ ├── desktop-check.ts │ ├── theme-map-check.ts │ └── android │ │ └── OriginImpletement │ │ └── theme-colors-and-pic-api.ts ├── db │ ├── mysql-use.ts │ └── models │ │ ├── init-models.ts │ │ ├── jump_to_theme.ts │ │ └── theme_editor_log.ts ├── bot │ └── bot.ts └── express │ └── http-config.ts ├── public ├── wallpapers │ ├── 0.jpg │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 20.jpg │ ├── 21.jpg │ ├── 22.jpg │ ├── 23.jpg │ ├── 24.jpg │ ├── 25.jpg │ ├── 26.jpg │ ├── 27.jpg │ ├── 28.jpg │ ├── 29.jpg │ ├── 3.jpg │ ├── 30.jpg │ ├── 31.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── model │ └── asakuravin.tdesktop-theme ├── classic-tdesktheme │ └── classic.tdesktop-theme ├── tempelete │ └── tohuemodle │ │ ├── android │ │ ├── black │ │ │ ├── black1 │ │ │ │ ├── 捕获.png │ │ │ │ └── TwilightEbony.attheme │ │ │ ├── black2 │ │ │ │ └── 捕获.png │ │ │ ├── black3 │ │ │ │ └── 捕获.png │ │ │ ├── black4 │ │ │ │ └── 捕获.png │ │ │ ├── black5 │ │ │ │ ├── 捕获.png │ │ │ │ └── Girl 1 @AloneSnowflake.attheme │ │ │ └── black6 │ │ │ │ ├── 捕获.png │ │ │ │ └── @Gumiho_tem1.attheme │ │ └── white │ │ │ ├── white1 │ │ │ ├── 捕获.png │ │ │ └── yusif.attheme │ │ │ ├── white2 │ │ │ ├── 捕获.png │ │ │ └── Orange Flower @AloneSnowflake.attheme │ │ │ ├── white3 │ │ │ ├── 捕获.png │ │ │ └── Day.attheme │ │ │ ├── white4 │ │ │ ├── 捕获.png │ │ │ └── @Gumiho_tem1.attheme │ │ │ └── white5 │ │ │ ├── 捕获.png │ │ │ └── Chestnut Chiffon.attheme │ │ └── desktop │ │ ├── black │ │ ├── black1 │ │ │ ├── 捕获.png │ │ │ └── yusif.tdesktop-theme │ │ ├── black2 │ │ │ ├── 捕获.png │ │ │ └── awesome.tdesktop-theme │ │ ├── black3 │ │ │ ├── 捕获.png │ │ │ └── awesome02.tdesktop-theme │ │ ├── black4 │ │ │ ├── 捕获.png │ │ │ └── awesome.tdesktop-theme │ │ ├── black5 │ │ │ ├── 捕获.png │ │ │ └── awesome.tdesktop-theme │ │ └── black6 │ │ │ ├── 捕获.png │ │ │ └── awesome.tdesktop-theme │ │ └── white │ │ ├── white1 │ │ ├── 捕获.png │ │ └── awesome.tdesktop-theme │ │ ├── white2 │ │ ├── 捕获.png │ │ └── awesome01.tdesktop-theme │ │ ├── white3 │ │ ├── 捕获.png │ │ └── awesome.tdesktop-theme │ │ └── white4 │ │ ├── 捕获.png │ │ └── awesome.tdesktop-theme └── assets │ └── colors.svg ├── makeWallpaper ├── photo_2025-08-20_01-17-07.jpg └── Rum Luster.attheme ├── .prettierrc.js ├── .dockerignore ├── .editorconfig ├── .gitignore ├── test2.ts ├── tsconfig.json ├── server.ts ├── package.json └── README.md /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/util/my-tdesk.js: -------------------------------------------------------------------------------- 1 | //桌面主题色彩算法研究 -------------------------------------------------------------------------------- /src/lib/atthemejs/.prettierrc.toml: -------------------------------------------------------------------------------- 1 | trailingComma = "all" 2 | -------------------------------------------------------------------------------- /public/wallpapers/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/0.jpg -------------------------------------------------------------------------------- /public/wallpapers/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/1.jpg -------------------------------------------------------------------------------- /public/wallpapers/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/10.jpg -------------------------------------------------------------------------------- /public/wallpapers/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/11.jpg -------------------------------------------------------------------------------- /public/wallpapers/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/12.jpg -------------------------------------------------------------------------------- /public/wallpapers/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/13.jpg -------------------------------------------------------------------------------- /public/wallpapers/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/14.jpg -------------------------------------------------------------------------------- /public/wallpapers/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/15.jpg -------------------------------------------------------------------------------- /public/wallpapers/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/16.jpg -------------------------------------------------------------------------------- /public/wallpapers/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/17.jpg -------------------------------------------------------------------------------- /public/wallpapers/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/18.jpg -------------------------------------------------------------------------------- /public/wallpapers/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/19.jpg -------------------------------------------------------------------------------- /public/wallpapers/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/2.jpg -------------------------------------------------------------------------------- /public/wallpapers/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/20.jpg -------------------------------------------------------------------------------- /public/wallpapers/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/21.jpg -------------------------------------------------------------------------------- /public/wallpapers/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/22.jpg -------------------------------------------------------------------------------- /public/wallpapers/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/23.jpg -------------------------------------------------------------------------------- /public/wallpapers/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/24.jpg -------------------------------------------------------------------------------- /public/wallpapers/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/25.jpg -------------------------------------------------------------------------------- /public/wallpapers/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/26.jpg -------------------------------------------------------------------------------- /public/wallpapers/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/27.jpg -------------------------------------------------------------------------------- /public/wallpapers/28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/28.jpg -------------------------------------------------------------------------------- /public/wallpapers/29.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/29.jpg -------------------------------------------------------------------------------- /public/wallpapers/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/3.jpg -------------------------------------------------------------------------------- /public/wallpapers/30.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/30.jpg -------------------------------------------------------------------------------- /public/wallpapers/31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/31.jpg -------------------------------------------------------------------------------- /public/wallpapers/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/4.jpg -------------------------------------------------------------------------------- /public/wallpapers/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/5.jpg -------------------------------------------------------------------------------- /public/wallpapers/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/6.jpg -------------------------------------------------------------------------------- /public/wallpapers/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/7.jpg -------------------------------------------------------------------------------- /public/wallpapers/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/8.jpg -------------------------------------------------------------------------------- /public/wallpapers/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/wallpapers/9.jpg -------------------------------------------------------------------------------- /public/model/asakuravin.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/model/asakuravin.tdesktop-theme -------------------------------------------------------------------------------- /makeWallpaper/photo_2025-08-20_01-17-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/makeWallpaper/photo_2025-08-20_01-17-07.jpg -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=types.js.map -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | tabWidth: 4, 5 | arrowParens: 'avoid', 6 | }; 7 | -------------------------------------------------------------------------------- /public/classic-tdesktheme/classic.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/classic-tdesktheme/classic.tdesktop-theme -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/variables.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A list of all variable names. 3 | */ 4 | declare const VARIABLES: string[]; 5 | export default VARIABLES; 6 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black1/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black1/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black2/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black2/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black3/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black3/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black4/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black4/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black5/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black5/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black6/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black6/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white1/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white1/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white2/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white2/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white3/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white3/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white4/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white4/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white5/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white5/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black1/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black1/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black2/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black2/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black3/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black3/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black4/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black4/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black5/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black5/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black6/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black6/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white1/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white1/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white2/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white2/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white3/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white3/捕获.png -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white4/捕获.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white4/捕获.png -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/fallbacks.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A fallback map used by Telegram. 3 | */ 4 | declare const FALLBACKS: Map; 5 | export default FALLBACKS; 6 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/node/fromFile.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from "../.."; 2 | declare const fromFile: (path: string) => Promise; 3 | export default fromFile; 4 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white3/Day.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white3/Day.attheme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white1/yusif.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white1/yusif.attheme -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/docker-compose* 6 | **/Dockerfile* 7 | **/node_modules 8 | README.md 9 | /public/myserver-bot-public/ 10 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black6/@Gumiho_tem1.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black6/@Gumiho_tem1.attheme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white4/@Gumiho_tem1.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white4/@Gumiho_tem1.attheme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black1/yusif.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black1/yusif.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black1/TwilightEbony.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black1/TwilightEbony.attheme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black2/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black2/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black4/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black4/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black5/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black5/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black6/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black6/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white1/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white1/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white3/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white3/awesome.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white4/awesome.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white4/awesome.tdesktop-theme -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white5/Chestnut Chiffon.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white5/Chestnut Chiffon.attheme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/black/black3/awesome02.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/black/black3/awesome02.tdesktop-theme -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/desktop/white/white2/awesome01.tdesktop-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/desktop/white/white2/awesome01.tdesktop-theme -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/defaultThemes/graphite.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | /** 3 | * Generates the Graphite theme. 4 | */ 5 | declare const graphite: () => Attheme; 6 | export default graphite; 7 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/browser/fromFile.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import Attheme from "../.."; 3 | declare const fromFile: (file: File) => Promise; 4 | export default fromFile; 5 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/defaultThemes/default.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | /** 3 | * Generates the Default theme. 4 | */ 5 | declare const defaultTheme: () => Attheme; 6 | export default defaultTheme; 7 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/black/black5/Girl 1 @AloneSnowflake.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/black/black5/Girl 1 @AloneSnowflake.attheme -------------------------------------------------------------------------------- /src/restore/pubmap.ts: -------------------------------------------------------------------------------- 1 | interface restoreThemeValue { 2 | tempName: string; 3 | themeName: string; 4 | } 5 | 6 | let map=new Map() 7 | export {restoreThemeValue} 8 | export default map 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .vscode/ 3 | i18n/ 4 | node_modules/ 5 | /TODO 6 | /pic/ 7 | /testdic/ 8 | /build/** 9 | /android-theme/ 10 | /test/** 11 | /log/app.log 12 | config.ts 13 | test.ts 14 | /public/myserver-bot-public/ 15 | -------------------------------------------------------------------------------- /public/tempelete/tohuemodle/android/white/white2/Orange Flower @AloneSnowflake.attheme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yusifmorley/ThemeFactory/HEAD/public/tempelete/tohuemodle/android/white/white2/Orange Flower @AloneSnowflake.attheme -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | import { RgbColor } from "@snejugal/color"; 2 | export declare type Color = RgbColor; 3 | export declare type ColorSignature = "hex" | "int"; 4 | export declare type VariableIterator = Iterable<[string, RgbColor]>; 5 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/defaultThemes/dark.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | import { PartialHsbColor } from "@snejugal/color"; 3 | /** 4 | * Generates the Dark theme. 5 | */ 6 | declare const dark: (accent: PartialHsbColor) => Attheme; 7 | export default dark; 8 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/defaultThemes/mono.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | import { PartialHsbColor } from "@snejugal/color"; 3 | /** 4 | * Generates the Mono theme. 5 | */ 6 | declare const mono: (accent: PartialHsbColor) => Attheme; 7 | export default mono; 8 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/defaultThemes/arctic.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | import { PartialHsbColor } from "@snejugal/color"; 3 | /** 4 | * Generates the Arctic theme. 5 | */ 6 | declare const arctic: (accent: PartialHsbColor) => Attheme; 7 | export default arctic; 8 | -------------------------------------------------------------------------------- /src/mianwrapper/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'wrapper/android/theme-colors-and-pic' 2 | declare module 'wrapper/preview/theme-preview' 3 | declare module 'attheme-default-values.js' 4 | declare module 'preview-maker.js' 5 | declare module 'render-pool.js' 6 | declare module 'make-desktop-theme-util' 7 | 8 | -------------------------------------------------------------------------------- /log/app.log.2025-02-20: -------------------------------------------------------------------------------- 1 | [2025-02-20T15:32:57.460] [INFO] F:\Project\webstormProject\ThemeFactory\theme-map-check.ts - gfrdrsgre 2 | [2025-02-20T15:32:57.481] [INFO] F:\Project\webstormProject\ThemeFactory\theme-map-check.ts - 安卓主题预览完整 3 | [2025-02-20T15:32:57.481] [INFO] F:\Project\webstormProject\ThemeFactory\theme-map-check.ts - 桌面主题预览完整 4 | -------------------------------------------------------------------------------- /src/lib/util/my-attheme.js: -------------------------------------------------------------------------------- 1 | //安卓主题色彩算法研究 2 | //安卓提供 三种颜色搭配 //随机颜色按钮 3 | //三种 颜色只是参考 4 | 5 | //主颜色 次颜色 字体颜色 6 | /* 7 | 具体步骤 8 | 1.对比度 计算(主颜色对 字体的对比度) 9 | 10 | */ 11 | let color1="#aaaaaa" 12 | let color2="#cbbbbb" 13 | 14 | const contrast = require('./contrast'); 15 | 16 | console.log(contrast.getContrastRatio(color1,color2)) 17 | -------------------------------------------------------------------------------- /test2.ts: -------------------------------------------------------------------------------- 1 | import {NAttheme} from "./src/lib/wapper/NAttheme"; 2 | 3 | import * as fs from "node:fs"; 4 | let buffer = fs.readFileSync("makeWallpaper/Rum Luster.attheme"); 5 | let nAttheme = new NAttheme(buffer); 6 | let wallPaer=fs.readFileSync("makeWallpaper/photo_2025-08-20_01-17-07.jpg") 7 | nAttheme.setWallpaper(wallPaer); 8 | fs.writeFileSync("Rum Luster.attheme",nAttheme.toFile()); 9 | -------------------------------------------------------------------------------- /src/lib/util/light-darkdem.ts: -------------------------------------------------------------------------------- 1 | //判断是否是亮色 2 | function is_light(rgb:string){ 3 | const hexColor = rgb.replace('#', ''); 4 | const r = parseInt(hexColor.substring(0, 2), 16); 5 | const g = parseInt(hexColor.substring(2, 4), 16); 6 | const b = parseInt(hexColor.substring(4, 6), 16); 7 | let yiq = (r * 299 + g * 587 + b * 114) / 1000 8 | return yiq >= 128 9 | } 10 | 11 | export default is_light 12 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/parseContents.d.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "./types"; 2 | interface ParseThemeResult { 3 | variables: Map; 4 | wallpaper?: string; 5 | } 6 | /** 7 | * Parses the .attheme contents. 8 | * @param contents The .attheme contents to parse. 9 | * @returns The parsed contents. 10 | */ 11 | declare const parseContents: (contents: string) => ParseThemeResult; 12 | export default parseContents; 13 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/node/toFile.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from "../.."; 2 | /** 3 | * Writes the theme into a file. 4 | * @param theme The theme to write. 5 | * @param path The file to write in. 6 | * @param colorSignature Either `hex` or `int`. 7 | * @returns Promised resolved when the file is written 8 | */ 9 | declare const toFile: (theme: Attheme, path: string, colorSignature?: "hex" | "int" | undefined) => Promise; 10 | export default toFile; 11 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/serializeTheme.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from "."; 2 | import { ColorSignature } from "./types"; 3 | /** 4 | * Serializes the theme. 5 | * @param theme The theme to serialize. 6 | * @param colorSignature The way the colors should be encoded, "hex" for 7 | * #aarrggbb and "int" for Java int color. 8 | * @returns The serialized theme. 9 | */ 10 | declare const serializeTheme: (theme: Attheme, colorSignature?: ColorSignature) => string; 11 | export default serializeTheme; 12 | -------------------------------------------------------------------------------- /src/mianwrapper/tool/colorcacu.ts: -------------------------------------------------------------------------------- 1 | function isAngleInRange(x, y, z) { 2 | let start = (x - y + 360) % 360; 3 | let end = (x + y) % 360; 4 | 5 | if (start < end) { 6 | return z >= start && z <= end; 7 | } else { 8 | return z >= start || z <= end; 9 | } 10 | } 11 | function adjustHue(h, delta) { 12 | let result = (h + delta) % 360; 13 | if (result < 0) result += 360; // 确保是正值 14 | return result; 15 | } 16 | 17 | export {isAngleInRange,adjustHue}; 18 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/browser/toBlob.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import Attheme from "../.."; 3 | /** 4 | * Creates a Blob to download the theme. 5 | * @param theme The theme to download. 6 | * @param name The theme's name. 7 | * @param colorSignature Either `hex` or `int`. 8 | * @returns The created blob the theme can be downloaded through. 9 | */ 10 | declare const toBlob: (theme: Attheme, name: string, colorSignature?: "hex" | "int" | undefined) => string; 11 | export default toBlob; 12 | -------------------------------------------------------------------------------- /src/lib/util/types.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'grammy'; 2 | import { Variables } from 'new-i18n/lib/types'; 3 | 4 | export interface I18nContext extends Context { 5 | theme?: Theme; 6 | i18n: (keyword: string, variables?: Variables) => string; 7 | } 8 | 9 | export interface Theme { 10 | photo: string | Buffer; 11 | colors: string[]; 12 | using: { 13 | label: string; 14 | color: string; 15 | }[]; 16 | } 17 | 18 | export type ThemeType = 'attheme' | 'tgios-theme' | 'tgx-theme'|'attheme-tran'; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "strict": true, 5 | "target": "ES2021", 6 | "esModuleInterop": true, 7 | "outDir": "build/", 8 | "skipLibCheck": true, 9 | "moduleResolution": "node", 10 | "noImplicitAny": false 11 | }, 12 | "ts-node": { 13 | "transpileOnly": true,// 跳过编译审查 14 | "files": true 15 | }, 16 | "include": [ 17 | "origin/", 18 | "main/mianwrapper/", 19 | "origin/preview/" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/themeToObject.d.ts: -------------------------------------------------------------------------------- 1 | import Attheme from ".."; 2 | import { Color } from "../types"; 3 | declare const IMAGE_KEY: unique symbol; 4 | interface ObjectTheme { 5 | [key: string]: Color; 6 | [IMAGE_KEY]?: string; 7 | } 8 | /** 9 | * Converts the new Attheme instance into the old one compatible with the old 10 | * versions of attheme-js. 11 | * @param theme The new Attheme instance. 12 | * @returns An object compatible with the old versions of attheme-js. 13 | */ 14 | declare const themeToObject: (theme: Attheme) => ObjectTheme; 15 | export default themeToObject; 16 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/node/fromFile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const fs = require("fs"); 5 | const __1 = require("../../index"); 6 | const fromFile = (path) => new Promise((resolve, reject) => { 7 | fs.readFile(path, `binary`, (error, contents) => { 8 | if (error) { 9 | reject(error); 10 | } 11 | else { 12 | resolve(new __1.default(contents)); 13 | } 14 | }); 15 | }); 16 | exports.default = fromFile; 17 | //# sourceMappingURL=fromFile.js.map 18 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | nohup npx ts-node server.ts 3 | ctrl + z 4 | bg 5 | disown -a 6 | */ 7 | import {bot} from "./src/bot/bot"; 8 | import {initHttp} from "./src/express/http-config"; 9 | import logger from "./src/lib/config/log_config"; 10 | let log=logger.getLogger(`${__filename}`); 11 | 12 | //启动http服务 13 | initHttp() 14 | //启动bot 15 | bot.start() 16 | 17 | process.on('uncaughtException', (err) => { 18 | log.error('未捕获的异常:', err); 19 | process.exit(1); 20 | }); 21 | 22 | process.on('unhandledRejection', (reason, promise) => { 23 | log.error('未捕获的异常:', reason); 24 | process.exit(1); 25 | }); 26 | -------------------------------------------------------------------------------- /src/lib/config/log_config.ts: -------------------------------------------------------------------------------- 1 | import log4js from "log4js" 2 | import process from "node:process"; 3 | 4 | const logConfig = { 5 | appenders: { 6 | console: { type: 'console' }, 7 | file: { 8 | type: 'file', 9 | filename: 'log/app.log', // 路径 10 | pattern:"yyyy-MM-dd", //精确到天 11 | compress: false, //压缩 12 | numBackups: 7, //7天 13 | }, 14 | }, 15 | categories: { 16 | default: { 17 | appenders: process.env.NODE_ENV!=="dev"?['file'] : ['console'], 18 | level: process.env.NODE_ENV!=="dev"?'info':'debug' 19 | } 20 | } 21 | }; 22 | 23 | const log=log4js.configure(logConfig); 24 | 25 | export default log; 26 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/browser/fromFile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const __1 = require("../.."); 5 | const fromFile = (file) => new Promise((resolve, reject) => { 6 | const reader = new FileReader(); 7 | reader.onload = () => { 8 | const chars = new Uint8Array(reader.result); 9 | let contents = ``; 10 | for (const char of chars) { 11 | contents += String.fromCharCode(char); 12 | } 13 | resolve(new __1.default(contents)); 14 | }; 15 | reader.onerror = () => reject(reader.error); 16 | reader.readAsArrayBuffer(file); 17 | }); 18 | exports.default = fromFile; 19 | //# sourceMappingURL=fromFile.js.map 20 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/node/toFile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const fs = require("fs"); 5 | /** 6 | * Writes the theme into a file. 7 | * @param theme The theme to write. 8 | * @param path The file to write in. 9 | * @param colorSignature Either `hex` or `int`. 10 | * @returns Promised resolved when the file is written 11 | */ 12 | const toFile = (theme, path, colorSignature) => new Promise((resolve, reject) => { 13 | fs.writeFile(path, theme.toString(colorSignature), `binary`, error => { 14 | if (error) { 15 | reject(error); 16 | } 17 | else { 18 | resolve(); 19 | } 20 | }); 21 | }); 22 | exports.default = toFile; 23 | //# sourceMappingURL=toFile.js.map -------------------------------------------------------------------------------- /src/mianwrapper/desktop/OriginImpletement/make-desktop-theme-util.ts: -------------------------------------------------------------------------------- 1 | //主题创建的核心逻辑 2 | //提供两种颜色 主次颜色 和背景 图片生成桌面主题 3 | // 主颜色 为windowbg 4 | // 次颜色 为字体颜色 5 | //我们把主题分为亮色 和暗色 两套 模板 6 | // @ts-ignore 7 | import JSZip from "jszip"; 8 | import bt from "./black-tm" 9 | import lt from "./light-tm" 10 | import loge from "../../../lib/config/log_config"; 11 | import is_light from "../../../lib/util/light-darkdem"; 12 | 13 | let log=loge.getLogger(`${__filename}`); 14 | export default async function makeThemeDesktop(colorArray:string[],bgbase64,flag=false) { 15 | log.info(`背景色为${colorArray[0]},强调色为${colorArray[1]}, 辅助色 ${colorArray[2]} 透明为 ${flag} }`) 16 | if (is_light(colorArray[0])){ 17 | //亮色 18 | return await lt(colorArray,bgbase64,flag) 19 | }else { 20 | //暗色 21 | return await bt(colorArray,bgbase64,flag) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/themeToObject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const IMAGE_KEY = Symbol.for(`image`); 4 | /** 5 | * Converts the new Attheme instance into the old one compatible with the old 6 | * versions of attheme-js. 7 | * @param theme The new Attheme instance. 8 | * @returns An object compatible with the old versions of attheme-js. 9 | */ 10 | const themeToObject = (theme) => { 11 | let object = {}; 12 | for (const [variable, value] of theme) { 13 | if (variable === `__proto__`) { 14 | continue; 15 | } 16 | object[variable] = value; 17 | } 18 | if (theme.hasWallpaper()) { 19 | object[IMAGE_KEY] = theme.getWallpaper(); 20 | } 21 | return object; 22 | }; 23 | exports.default = themeToObject; 24 | //# sourceMappingURL=themeToObject.js.map -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/tools/browser/toBlob.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | /** 5 | * Creates a Blob to download the theme. 6 | * @param theme The theme to download. 7 | * @param name The theme's name. 8 | * @param colorSignature Either `hex` or `int`. 9 | * @returns The created blob the theme can be downloaded through. 10 | */ 11 | const toBlob = (theme, name, colorSignature) => { 12 | const serialized = theme.toString(colorSignature); 13 | const length = serialized.length; 14 | const buffer = new Uint8Array(length); 15 | for (let i = 0; i < length; i++) { 16 | buffer[i] = serialized.codePointAt(i); 17 | } 18 | const blob = URL.createObjectURL(new File([buffer], name)); 19 | return blob; 20 | }; 21 | exports.default = toBlob; 22 | //# sourceMappingURL=toBlob.js.map 23 | -------------------------------------------------------------------------------- /src/lib/util/colors.ts: -------------------------------------------------------------------------------- 1 | import getColors from 'get-image-colors'; 2 | import { Theme } from './types'; 3 | import ntc from '@youtwitface/ntcjs'; 4 | 5 | export const labelColors = ( 6 | colors: Theme['using'], 7 | includeLabel = true, 8 | ): string[] => { 9 | return colors.map(x => { 10 | const color = x.color.toUpperCase(); 11 | return includeLabel && x.label ? `${x.label} (${color})` : color; 12 | }); 13 | }; 14 | 15 | export const getImageColors = async ( 16 | buffer: string | Buffer, 17 | type?: string, 18 | ): Promise => { 19 | let colors; 20 | if (typeof buffer === 'string') { 21 | colors = await getColors(buffer); 22 | } else { 23 | colors = await getColors(buffer, type ?? 'image/jpeg'); 24 | } 25 | 26 | return colors.map(color => color.hex()); 27 | }; 28 | 29 | export const getColorName = (color: string): string => { 30 | return ntc(color)[1]; 31 | }; 32 | -------------------------------------------------------------------------------- /src/db/mysql-use.ts: -------------------------------------------------------------------------------- 1 | import {Sequelize} from "sequelize"; 2 | import {dbConfig,dbConfigPro,DBConfig} from "../../config"; 3 | import process from "node:process"; 4 | import {initModels, theme_editor_logAttributes} from "./models/init-models"; 5 | import logger from "../lib/config/log_config"; 6 | let log=logger.getLogger(`${__filename}`); 7 | let db:DBConfig = dbConfig; 8 | if(process.env.NODE_ENV!=="dev"){ 9 | db=dbConfigPro 10 | } 11 | const sequelize = new Sequelize('theme_factory', 12 | db.username, 13 | db.password, 14 | { 15 | host: 'localhost', 16 | dialect: 'mysql',/* one of 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'db2' | 'snowflake' | 'oracle' */ 17 | logging:false 18 | }); 19 | sequelize.authenticate().then(()=>{ 20 | log.info("数据库连接成功") 21 | }); 22 | let iMod = initModels(sequelize); 23 | iMod.theme_editor_log.removeAttribute("id") 24 | iMod.jump_to_theme.removeAttribute("id") 25 | 26 | export default iMod ; 27 | -------------------------------------------------------------------------------- /src/db/models/init-models.ts: -------------------------------------------------------------------------------- 1 | import type { Sequelize } from "sequelize"; 2 | import { jump_to_theme as _jump_to_theme } from "./jump_to_theme"; 3 | import type { jump_to_themeAttributes, jump_to_themeCreationAttributes } from "./jump_to_theme"; 4 | import { theme_editor_log as _theme_editor_log } from "./theme_editor_log"; 5 | import type { theme_editor_logAttributes, theme_editor_logCreationAttributes } from "./theme_editor_log"; 6 | 7 | export { 8 | _jump_to_theme as jump_to_theme, 9 | _theme_editor_log as theme_editor_log, 10 | }; 11 | 12 | export type { 13 | jump_to_themeAttributes, 14 | jump_to_themeCreationAttributes, 15 | theme_editor_logAttributes, 16 | theme_editor_logCreationAttributes, 17 | }; 18 | 19 | export function initModels(sequelize: Sequelize) { 20 | const jump_to_theme = _jump_to_theme.initModel(sequelize); 21 | const theme_editor_log = _theme_editor_log.initModel(sequelize); 22 | 23 | 24 | return { 25 | jump_to_theme: jump_to_theme, 26 | theme_editor_log: theme_editor_log, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/preview/render-pool.js: -------------------------------------------------------------------------------- 1 | const maker = require(`./preview-maker`); 2 | 3 | const MAX_RENDERERS_AT_ONCE = 3; 4 | let renderersNumber = 0; 5 | 6 | const queue = []; 7 | 8 | const render = async ({ theme, name, template, resolve, reject }) => { 9 | renderersNumber++; 10 | 11 | try { 12 | 13 | let preview = await maker.makePrev(theme, name, ``, template); 14 | 15 | resolve(preview); 16 | } catch (error) { 17 | reject(error); 18 | } 19 | 20 | renderersNumber--; 21 | 22 | if (queue.length > 0) { 23 | await render(queue.shift()); 24 | } 25 | }; 26 | 27 | module.exports = (previewParameters) => 28 | new Promise((resolve, reject) => { 29 | let renderParameters = { 30 | ...previewParameters, 31 | resolve, 32 | reject, 33 | }; 34 | 35 | if (renderersNumber < MAX_RENDERERS_AT_ONCE) { 36 | render(renderParameters).then((result) => { 37 | resolve(result); 38 | }).catch((error) => { 39 | reject(error); // 捕获并传递错误 40 | }); 41 | } else { 42 | queue.push(renderParameters); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/lib/atthemejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atthemejs", 3 | "version": "3.0.0", 4 | "description": "A small package for working with .attheme files in JavaScript.", 5 | "author": "SnejUgal (snejugal.ru)", 6 | "license": "MIT", 7 | "keywords": [ 8 | "telegram", 9 | "attheme" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/SnejUgal/attheme-js.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/SnejUgal/attheme-js/issues" 17 | }, 18 | "homepage": "https://github.com/SnejUgal/attheme-js#readme", 19 | "main": "lib/index.js", 20 | "scripts": { 21 | "build": "npx tsc -p .", 22 | "test": "npm run build && npx ava", 23 | "format": "npx prettier --write \"{src,test}/**/*.{js,ts}\"", 24 | "prepublishOnly": "rm -rf lib && npm run test" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^12.7.12", 28 | "ava": "^2.4.0", 29 | "jimp": "^0.8.4", 30 | "jsdom": "^15.1.1", 31 | "prettier": "^1.18.2", 32 | "typescript": "^3.6.4" 33 | }, 34 | "dependencies": { 35 | "@snejugal/color": "^2.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/atthemejs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SnejUgal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/desktop/black.ts: -------------------------------------------------------------------------------- 1 | import {DeBaseThemeOperation, ThemeType} from "../base-theme-operation"; 2 | 3 | export namespace DesktopBlack{ 4 | class BlackThemeBase extends DeBaseThemeOperation{ 5 | constructor( 6 | public id: string, 7 | public tP: string, 8 | public pP: string = "捕获.PNG", // 默认值 9 | public mainColorSelect: string, 10 | public colorType:ThemeType=ThemeType.Simple 11 | ) {super("black",id,tP,pP,mainColorSelect,colorType);} 12 | } 13 | // TODO black2 black3 无效 14 | const blackThemes = [ 15 | new BlackThemeBase("black1", "yusif.tdesktop-theme", "捕获.PNG", "dialogsTextFg"), 16 | new BlackThemeBase("black4", "awesome.tdesktop-theme", "捕获.PNG", "sideBarIconFgActive"), 17 | new BlackThemeBase("black5", "awesome.tdesktop-theme", "捕获.PNG", "uiAccent"), 18 | new BlackThemeBase("black6", "awesome.tdesktop-theme", "捕获.PNG", "windowBgActive") 19 | ]; 20 | // 初始化 Map 并设置数据 21 | export const desktopBlackMap = new Map( 22 | blackThemes.map(theme => [theme.id, theme]) 23 | ); 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/db/models/jump_to_theme.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | import { DataTypes, Model, Optional } from 'sequelize'; 3 | 4 | export interface jump_to_themeAttributes { 5 | type?: number; 6 | theme_name?: string; 7 | date?: Date; 8 | } 9 | 10 | export type jump_to_themeOptionalAttributes = "type" | "theme_name" | "date"; 11 | export type jump_to_themeCreationAttributes = Optional; 12 | 13 | export class jump_to_theme extends Model implements jump_to_themeAttributes { 14 | type?: number; 15 | theme_name?: string; 16 | date?: Date; 17 | 18 | static initModel(sequelize: Sequelize.Sequelize): typeof jump_to_theme { 19 | return jump_to_theme.init({ 20 | type: { 21 | type: DataTypes.TINYINT.UNSIGNED, 22 | allowNull: true 23 | }, 24 | theme_name: { 25 | type: DataTypes.STRING(100), 26 | allowNull: true 27 | }, 28 | date: { 29 | type: DataTypes.DATE, 30 | allowNull: true 31 | } 32 | }, { 33 | sequelize, 34 | tableName: 'jump_to_theme', 35 | timestamps: false 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/atthemejs/readme.md: -------------------------------------------------------------------------------- 1 | # `attheme-js` 2 | 3 | A package for working with .attheme files in JavaScript. It fully supports the 4 | .attheme format. 5 | 6 | ## Installing 7 | 8 | ```bash 9 | npm i attheme-js 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```ts 15 | import Attheme from "attheme-js"; 16 | 17 | const theme = new Attheme(` 18 | divider=#000000 19 | checkbox=-1 20 | 21 | WPS 22 | Pretend there is a cats wallpaper here 23 | WPE 24 | `); 25 | 26 | console.log(theme.get(`divider`)); // { red: 0, green: 0, blue: 0, alpha: 255 } 27 | theme.set(`checkbox`, { 28 | red: 255, 29 | green: 146, 30 | blue: 13, 31 | alpha: 7, 32 | }); 33 | console.log(theme.get(`checkbox`)); // { red: 255, green: 146, blue: 13, alpha: 7 } 34 | 35 | console.log(theme.getWallpaper()); // Pretend there is a cats wallpaper here 36 | 37 | console.log(theme.toString(`hex`)); /* 38 | divider=#ff000000 39 | checkbox=#ffffffff 40 | 41 | WPS 42 | Pretend there is a cats wallpaper here 43 | WPE 44 | 45 | */ 46 | ``` 47 | 48 | For the API documentation and tools `attheme-js` provides out of the box, please see [the documentation section on our Wiki][documentation]. 49 | 50 | [documentation]: https://github.com/SnejUgal/attheme-js/wiki/Documentation 51 | -------------------------------------------------------------------------------- /src/db/models/theme_editor_log.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | import { DataTypes, Model, Optional } from 'sequelize'; 3 | 4 | export interface theme_editor_logAttributes { 5 | kind?: number; 6 | ip?: string; 7 | date?: Date; 8 | } 9 | 10 | export type theme_editor_logOptionalAttributes = "kind" | "ip" | "date"; 11 | export type theme_editor_logCreationAttributes = Optional; 12 | 13 | export class theme_editor_log extends Model implements theme_editor_logAttributes { 14 | kind?: number; 15 | ip?: string; 16 | date?: Date; 17 | 18 | 19 | static initModel(sequelize: Sequelize.Sequelize): typeof theme_editor_log { 20 | return theme_editor_log.init({ 21 | kind: { 22 | type: DataTypes.TINYINT.UNSIGNED, 23 | allowNull: true, 24 | comment: "种类" 25 | }, 26 | ip: { 27 | type: DataTypes.STRING(100), 28 | allowNull: true 29 | }, 30 | date: { 31 | type: DataTypes.DATE, 32 | allowNull: true 33 | } 34 | }, { 35 | sequelize, 36 | tableName: 'theme_editor_log', 37 | timestamps: false 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/android/black.ts: -------------------------------------------------------------------------------- 1 | import {AnBaseThemeOperation, ThemeType} from "../base-theme-operation"; 2 | 3 | export namespace AndroidBlack { 4 | class BlackThemeBase extends AnBaseThemeOperation{ 5 | constructor( 6 | public id: string, //black{number} 7 | public tP: string, //主题名 8 | public pP: string = "捕获.PNG", // 默认值 9 | public mainColorSelect: string, 10 | public colorType:ThemeType=ThemeType.Simple 11 | ) { 12 | super("black",id,tP,pP,mainColorSelect,colorType); 13 | } 14 | } 15 | 16 | const blackThemes = [ 17 | new BlackThemeBase("black1", "TwilightEbony.attheme", "捕获.PNG", "actionBarDefaultTitle"), 18 | new BlackThemeBase("black2", "@Gumiho_tem2.attheme", "捕获.PNG", "actionBarDefaultTitle"), 19 | new BlackThemeBase("black3", "Gojo Purple Theme @aestheticpicsuwu.attheme", "捕获.PNG", "actionBarTabLine"), 20 | new BlackThemeBase("black4", "@Gumiho_tem2.attheme", "捕获.PNG", "actionBarDefaultTitle"), 21 | new BlackThemeBase("black5", "Girl 1 @AloneSnowflake.attheme", "捕获.PNG", "actionBarTabLine"), 22 | new BlackThemeBase("black6", "@Gumiho_tem1.attheme", "捕获.PNG", "actionBarDefault") 23 | ]; 24 | 25 | // 初始化 Map 并设置数据 26 | export const androidBlackMap = new Map( 27 | blackThemes.map(theme => [theme.id, theme]) 28 | ); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/lib/util/contrast.js: -------------------------------------------------------------------------------- 1 | function getContrastRatio(color1, color2) { 2 | // 获取两种颜色的RGB值 3 | const getColorRGB = color => { 4 | const hexColor = color.replace('#', ''); 5 | const r = parseInt(hexColor.substring(0, 2), 16); 6 | const g = parseInt(hexColor.substring(2, 4), 16); 7 | const b = parseInt(hexColor.substring(4, 6), 16); 8 | return [r / 255, g / 255, b / 255]; 9 | } 10 | 11 | const rgb1 = getColorRGB(color1); 12 | const rgb2 = getColorRGB(color2); 13 | 14 | // 计算相对亮度 15 | const getRelativeLuminance = rgb => { 16 | const [r, g, b] = rgb.map(value => { 17 | if (value <= 0.03928) { 18 | return value / 12.92; 19 | } else { 20 | return Math.pow((value + 0.055) / 1.055, 2.4); 21 | } 22 | }); 23 | return 0.2126 * r + 0.7152 * g + 0.0722 * b; 24 | } 25 | 26 | const luminance1 = getRelativeLuminance(rgb1); 27 | 28 | const luminance2 = getRelativeLuminance(rgb2); 29 | 30 | // 计算对比度 31 | return luminance1 > luminance2 ? (luminance1 + 0.05) / (luminance2 + 0.05) : (luminance2 + 0.05) / (luminance1 + 0.05); 32 | 33 | // 返回对比度并保留两位小数 34 | } 35 | 36 | module.exports={ getContrastRatio } 37 | 38 | // 示例使用: 39 | // const color1 = "#000000"; // 红色 40 | // const color2 = "#ffffff"; // 绿色 41 | // 42 | // const contrastRatio = getContrastRatio(color1, color2); 43 | // console.log(`颜色1和颜色2的对比度为:${contrastRatio}`); 44 | -------------------------------------------------------------------------------- /src/mianwrapper/desktop/OriginImpletement/theme-desktop-api.ts: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | import url from "url"; 3 | import makeThemeDesktop from "./make-desktop-theme-util"; 4 | import {TdesktopTheme} from "tdesktop-theme/node"; 5 | import overRefine from "./override"; 6 | import loge from "../../../lib/config/log_config"; 7 | 8 | let log=loge.getLogger(`${__filename}`); 9 | 10 | export async function basePicCreateDesktop(req:http.IncomingMessage, res:http.ServerResponse){ 11 | // @ts-ignore 12 | let body= req.body; //base64 格式 13 | log.info(`正在进行桌面主题创建 url 为${req.url}`) 14 | try { 15 | // @ts-ignore 16 | const urlObject = url.parse(req.url); 17 | const { pathname,query } = urlObject; 18 | const method = req.method; 19 | if (method==='POST'&&pathname==='/tdesktop-create'){ 20 | // log.info(body) 21 | const picObj=body; 22 | let buffer = Buffer.from(picObj?.picb,'base64'); 23 | await makeThemeDesktop(picObj?.colors,buffer,picObj?.flag).then(e=>{ 24 | let tdesktop = new TdesktopTheme(e) 25 | //overRefine(tdesktop) 26 | let uint8Array = tdesktop.toZipBytes(); 27 | res.end(Buffer.from(uint8Array)) 28 | }); 29 | //返回的时二进制 最后默认响应的是二进制 30 | }else { 31 | return; 32 | } 33 | }catch (e){ 34 | log.error(e) 35 | res.end("fail") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/base-theme-info.ts: -------------------------------------------------------------------------------- 1 | //获取所有模板 2 | import {ThemeType} from "./base-theme-operation"; 3 | import {AndroidBlack} from "./android/black"; 4 | import {AndroidWhite} from "./android/white"; 5 | import {DesktopBlack} from "./desktop/black"; 6 | import {DesktopWhite} from "./desktop/white"; 7 | 8 | function getTemplateInfo() { 9 | let tree={ 10 | android_black:[...AndroidBlack.androidBlackMap.keys()], 11 | android_white:[...AndroidWhite.androidWhiteMap.keys()], 12 | desktop_black:[...DesktopBlack.desktopBlackMap.keys()], 13 | desktop_white:[...DesktopWhite.desktopWhiteMap.keys()], 14 | } 15 | return tree; 16 | } 17 | 18 | function getDeskThemeTypeInfo() { 19 | let themeMap=new Map(); 20 | AndroidBlack.androidBlackMap.forEach((android)=>{ 21 | themeMap.set(android.id,android.colorType) 22 | }) 23 | AndroidWhite.androidWhiteMap.forEach((android)=>{ 24 | themeMap.set(android.id,android.colorType) 25 | }) 26 | DesktopBlack.desktopBlackMap.forEach((desk)=>{ 27 | themeMap.set("D"+desk.id,desk.colorType) 28 | }) 29 | DesktopWhite.desktopWhiteMap.forEach((desk)=>{ 30 | themeMap.set("D"+desk.id,desk.colorType) 31 | }) 32 | return themeMap; 33 | } 34 | 35 | function getThemeInfo(){ 36 | let deskThemeTypeInfo = getDeskThemeTypeInfo(); 37 | let templateInfo = getTemplateInfo(); 38 | Object.assign(templateInfo,{map:JSON.stringify(Array.from(deskThemeTypeInfo.entries()))}); 39 | return templateInfo; 40 | } 41 | 42 | export default getThemeInfo(); 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/serializeTheme.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Serializes the color. 5 | * @param color The color to serialize. 6 | * @param colorSignature The way the color should be encoded, "hex" for 7 | * #aarrggbb and "int" for Java int color. 8 | * @returns The serialized color. 9 | */ 10 | const serializeColor = (color, colorSignature) => { 11 | const red = color.red.toString(16).padStart(2, `0`); 12 | const green = color.green.toString(16).padStart(2, `0`); 13 | const blue = color.blue.toString(16).padStart(2, `0`); 14 | const alpha = color.alpha.toString(16).padStart(2, `0`); 15 | const hex = `${alpha}${red}${green}${blue}`; 16 | if (colorSignature === `hex`) { 17 | return `#${hex}`; 18 | } 19 | return String(Number.parseInt(hex, 16) << 0); 20 | }; 21 | /** 22 | * Serializes the theme. 23 | * @param theme The theme to serialize. 24 | * @param colorSignature The way the colors should be encoded, "hex" for 25 | * #aarrggbb and "int" for Java int color. 26 | * @returns The serialized theme. 27 | */ 28 | const serializeTheme = (theme, colorSignature = `int`) => { 29 | let result = ``; 30 | for (const [variable, color] of theme) { 31 | if (variable===undefined) { 32 | continue 33 | } 34 | const hex = serializeColor(color, colorSignature); 35 | result += `${variable}=${hex}\n`; 36 | } 37 | if (theme.hasWallpaper()) { 38 | result += `\nWPS\n${theme.getWallpaper()}\nWPE\n`; 39 | } 40 | return result; 41 | }; 42 | exports.default = serializeTheme; 43 | //# sourceMappingURL=serializeTheme.js.map 44 | -------------------------------------------------------------------------------- /src/lib/variables/helpers.ts: -------------------------------------------------------------------------------- 1 | import Color from 'color'; 2 | 3 | interface ThemeData { 4 | background: string; 5 | filling: string; 6 | text: string; 7 | backgroundText: string; 8 | secondaryText: string; 9 | primary: string; 10 | textOnPrimary: string; 11 | themeIsLight: boolean; 12 | bubbleOutColor: string; 13 | } 14 | 15 | export const isLight = (color: string): boolean => Color(color).isLight(); 16 | 17 | export const adjustBrightness = ( 18 | color: string, 19 | ratio: number, 20 | invert = false, 21 | ): string => { 22 | const object = Color(color); 23 | 24 | return object 25 | .lightness(object.lightness() + (invert ? -ratio : ratio)) 26 | .hex(); 27 | }; 28 | 29 | export const getForegroundColor = (background: string): string => 30 | isLight(background) ? adjustBrightness(background, -45) : '#ffffff'; 31 | 32 | export const themeData = ([filling, text, primary]: string[]): ThemeData => { 33 | const themeIsLight = isLight(filling); 34 | const textOnPrimary = getForegroundColor(primary); 35 | const background = adjustBrightness(filling, -6.5); 36 | const secondaryText = Color(filling).mix(Color(text), 0.4).hex(); 37 | const backgroundText = adjustBrightness(secondaryText, -10, themeIsLight); 38 | const bubbleOutColor = adjustBrightness( 39 | themeIsLight ? primary : filling, 40 | themeIsLight ? 41 : -3, 41 | ); 42 | 43 | return { 44 | background, 45 | filling, 46 | text, 47 | backgroundText, 48 | secondaryText, 49 | primary, 50 | textOnPrimary, 51 | themeIsLight, 52 | bubbleOutColor, 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /src/mianwrapper/desktop/OriginImpletement/override.ts: -------------------------------------------------------------------------------- 1 | import {Color, TdesktopTheme} from "tdesktop-theme/node"; 2 | import * as fs from "fs"; 3 | 4 | let path="public/model/asakuravin.tdesktop-theme" 5 | function getColor(td:TdesktopTheme,str:string){ 6 | let variable = td.getVariable(str); 7 | if (typeof variable==='string'){ 8 | return getColor(td,str) 9 | } 10 | else { 11 | return variable 12 | } 13 | } 14 | 15 | export default function overRefine(vatheme:TdesktopTheme){ 16 | 17 | vatheme.setVariable("sideBarBg",vatheme.getVariable("dialogsBg")) 18 | //侧边栏 活动背景 聊天列表 激活活动背景 19 | vatheme.setVariable("sideBarBgActive",vatheme.getVariable("dialogsBgActive")) 20 | //侧边字体 为聊天列表 群组名称 21 | vatheme.setVariable("sideBarTextFg",vatheme.getVariable("dialogsNameFg")) 22 | //侧边活动字体 为聊天列表 群组名称 激活时字体 23 | vatheme.setVariable("sideBarTextFgActive",vatheme.getVariable("dialogsNameFgActive")) 24 | //侧边栏小圆圈背景 25 | vatheme.setVariable("sideBarBadgeBg",vatheme.getVariable("dialogsUnreadBg")) 26 | //侧边栏小圆圈字体 27 | vatheme.setVariable("sideBarBadgeFg",vatheme.getVariable("dialogsUnreadFg")) 28 | //dialogsUnreadFgActive 29 | //侧边栏 图标背景 为聊天列表 群组名称 30 | vatheme.setVariable("sideBarIconFg",vatheme.getVariable("dialogsNameFg")) 31 | //侧边栏 活动图标背景 为聊天列表 群组名称 激活 32 | vatheme.setVariable("sideBarIconFgActive",vatheme.getVariable("dialogsNameFgActive")) 33 | 34 | vatheme.setVariable("historyComposeAreaBg",vatheme.getVariable("dialogsBg")) 35 | 36 | vatheme.setVariable("historyReplyBg",vatheme.getVariable("dialogsBg")) 37 | 38 | vatheme.setVariable("historyComposeButtonBg",vatheme.getVariable("historyComposeAreaBg")) 39 | 40 | 41 | return vatheme 42 | } 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/lib/util/con-tar.js: -------------------------------------------------------------------------------- 1 | function findIntermediateColor(color1, color2, targetContrast) { 2 | const getRGBArray = color => { 3 | const hexColor = color.replace('#', ''); 4 | const r = parseInt(hexColor.substring(0, 2), 16); 5 | const g = parseInt(hexColor.substring(2, 4), 16); 6 | const b = parseInt(hexColor.substring(4, 6), 16); 7 | return [r, g, b]; 8 | } 9 | 10 | const rgb1 = getRGBArray(color1); 11 | const rgb2 = getRGBArray(color2); 12 | 13 | // 计算目标相对亮度 14 | const getTargetLuminance = (luminance1, luminance2, targetContrast) => { 15 | if (luminance1 > luminance2) { 16 | return ((luminance1 / targetContrast) + luminance2) / (1 + (1 / targetContrast)); 17 | } else { 18 | return (luminance1 + (luminance2 * targetContrast)) / (1 + targetContrast); 19 | } 20 | } 21 | 22 | const luminance1 = 0.2126 * (rgb1[0] / 255) + 0.7152 * (rgb1[1] / 255) + 0.0722 * (rgb1[2] / 255); 23 | const luminance2 = 0.2126 * (rgb2[0] / 255) + 0.7152 * (rgb2[1] / 255) + 0.0722 * (rgb2[2] / 255); 24 | 25 | const targetLuminance = getTargetLuminance(luminance1, luminance2, targetContrast); 26 | 27 | // 根据目标相对亮度找到中间颜色 28 | const r = Math.round((targetLuminance / 0.2126) * 255); 29 | const g = Math.round((targetLuminance / 0.7152) * 255); 30 | const b = Math.round((targetLuminance / 0.0722) * 255); 31 | 32 | const intermediateColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; 33 | return intermediateColor; 34 | } 35 | 36 | // 示例使用: 37 | const color1 = "#000000"; // 红色 38 | const color2 = "#ffffff"; // 绿色 39 | const targetContrast = 7; 40 | 41 | const intermediateColor = findIntermediateColor(color1, color2, targetContrast); 42 | console.log(`中间颜色:${intermediateColor}`); 43 | -------------------------------------------------------------------------------- /src/lib/util/rgb-hsl.js: -------------------------------------------------------------------------------- 1 | function rgbToHsl(color) { 2 | 3 | const hexColor = color.replace('#', ''); 4 | const r = parseInt(hexColor.substring(0, 2), 16)/255; 5 | const g = parseInt(hexColor.substring(2, 4), 16)/255; 6 | const b = parseInt(hexColor.substring(4, 6), 16)/255; 7 | 8 | const max = Math.max(r, g, b); 9 | const min = Math.min(r, g, b); 10 | 11 | let h, s, l = (max + min) / 2; 12 | 13 | if (max === min) { 14 | h = s = 0; // 灰色 15 | } else { 16 | const d = max - min; 17 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 18 | switch (max) { 19 | case r: 20 | h = (g - b) / d + (g < b ? 6 : 0); 21 | break; 22 | case g: 23 | h = (b - r) / d + 2; 24 | break; 25 | case b: 26 | h = (r - g) / d + 4; 27 | break; 28 | } 29 | h /= 6; 30 | } 31 | 32 | h = Math.round(h * 360); 33 | s = Math.round(s * 100); 34 | l = Math.round(l * 100); 35 | 36 | return [h, s, l]; 37 | } 38 | 39 | 40 | function hslToRgb(h, s, l) { 41 | h /= 360; 42 | s /= 100; 43 | l /= 100; 44 | 45 | let r, g, b; 46 | 47 | if (s === 0) { 48 | r = g = b = l; // 饱和度为0,即灰色 49 | } else { 50 | const hue2rgb = (p, q, t) => { 51 | if (t < 0) t += 1; 52 | if (t > 1) t -= 1; 53 | if (t < 1 / 6) return p + (q - p) * 6 * t; 54 | if (t < 1 / 2) return q; 55 | if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 56 | return p; 57 | }; 58 | 59 | const q = l < 0.5 ? l * (1 + s) : l + s - l * s; 60 | const p = 2 * l - q; 61 | r = hue2rgb(p, q, h + 1 / 3); 62 | g = hue2rgb(p, q, h); 63 | b = hue2rgb(p, q, h - 1 / 3); 64 | } 65 | 66 | r = Math.round(r * 255); 67 | g = Math.round(g * 255); 68 | b = Math.round(b * 255); 69 | 70 | return `#${r}${g}${b}`; 71 | } 72 | module.exports={rgbToHsl,hslToRgb} 73 | -------------------------------------------------------------------------------- /src/mianwrapper/preview/theme-preview-api.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import url from 'url'; 3 | import * as http from 'http'; 4 | 5 | // @ts-ignore 6 | import render from '../../lib/preview/render-pool.js'; 7 | 8 | // @ts-ignore 9 | import { 10 | MINIMALISTIC_TEMPLATE,//移动端主题 11 | REGULAR_TEMPLATE, //移动端主题 12 | NEW_TEMPLATE, 13 | DESKTOP_TEMPLATE, //桌面模板 14 | } from "../../lib/preview/preview-maker.js"; 15 | import loge from "../../lib/config/log_config"; 16 | 17 | let log=loge.getLogger(`${__filename}`); 18 | export async function createPreview(req:http.IncomingMessage, res:http.ServerResponse) { 19 | // 获取url的各个部分 20 | // url.parse可以将req.url解析成一个对象 21 | // 里面包含有pathname和querystring等 22 | // @ts-ignore 23 | const urlObject = url.parse(req.url); 24 | log.info(`创建颜色预览 url 为 ${req.url}`) 25 | const { pathname,query } = urlObject; 26 | 27 | const method = req.method; 28 | 29 | let name: string; 30 | let theme=null; 31 | const body: any[] | readonly Uint8Array[]= []; 32 | let template:any; 33 | if (method === 'POST'&&pathname==='/android') { 34 | template=MINIMALISTIC_TEMPLATE; 35 | name=query.split('=')[1]; 36 | 37 | } 38 | else if (method === 'POST'&&pathname==='/desktop'){ 39 | template=DESKTOP_TEMPLATE; 40 | name= query.split('=')[1]; 41 | }else 42 | return; 43 | 44 | // @ts-ignore 45 | name =decodeURIComponent(name).replaceAll('.attheme',''); 46 | req.on('data', chunk => { 47 | // @ts-ignore 48 | body.push(chunk); 49 | }); 50 | req.on('end' ,async ()=>{ 51 | // @ts-ignore 52 | theme=Buffer.concat(body); 53 | await render({ 54 | theme, 55 | name, 56 | template, 57 | }).then((preview: any)=>{ 58 | res.setHeader('Content-Type', 'application/octet-stream'); 59 | res.write(preview); 60 | res.end(); 61 | }).catch((e: any)=>{ 62 | res.end('fail'); 63 | }); 64 | }); 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/lib/util/themes.ts: -------------------------------------------------------------------------------- 1 | import Attheme from "../atthemejs"; 2 | import { ThemeType } from './types'; 3 | import atthemeVariables from '../variables/attheme'; 4 | import tgiosVariables from '../variables/tgios-theme'; 5 | import tgxVariables from '../variables/tgx-theme'; 6 | import { getColorName } from './colors'; 7 | //此文件使用了attheme 3.0.0 8 | export const getThemeName = (background: string, primary: string): string => 9 | `${getColorName(primary)} on ${getColorName(background)}`; 10 | 11 | export const createTheme = ({ 12 | username, 13 | image, 14 | name, 15 | colors, 16 | type, 17 | }: { 18 | username: string; 19 | image: Buffer; 20 | name: string; 21 | colors: string[]; 22 | type: ThemeType; 23 | }): string => { 24 | switch (type) { 25 | case 'attheme': { 26 | const variables = atthemeVariables(colors); 27 | 28 | const theme = new Attheme(variables); 29 | 30 | theme.setWallpaper(image.toString('binary')); 31 | 32 | return theme.toString('int'); 33 | } 34 | case 'attheme-tran':{ 35 | 36 | const variables = atthemeVariables(colors); 37 | 38 | let theme = new Attheme(variables); 39 | 40 | theme.setWallpaper(image.toString('binary')); 41 | 42 | 43 | theme= setvalue(theme,'chat_inBubble',98) 44 | theme= setvalue(theme,'chat_messagePanelBackground',98) 45 | 46 | return theme.toString('int'); 47 | } 48 | 49 | case 'tgios-theme': { 50 | return tgiosVariables(name, colors); 51 | } 52 | 53 | case 'tgx-theme': { 54 | return tgxVariables(name, colors, username); 55 | } 56 | 57 | default: 58 | throw new TypeError(`Unknown theme type: ${type}`); 59 | } 60 | }; 61 | function setvalue(theme:Attheme,str:string,alpha:number){ 62 | let key= theme.get(str) 63 | if(key!==null){ 64 | key.alpha=alpha; 65 | theme.set(str,key); 66 | } 67 | return theme 68 | } 69 | -------------------------------------------------------------------------------- /src/mianwrapper/android-check.ts: -------------------------------------------------------------------------------- 1 | import render from '../lib/preview/render-pool.js'; 2 | import { 3 | MINIMALISTIC_TEMPLATE,//移动端主题 4 | REGULAR_TEMPLATE, //移动端主题 5 | NEW_TEMPLATE, 6 | DESKTOP_TEMPLATE, //桌面模板 7 | } from "../lib/preview/preview-maker.js"; 8 | import fs from "fs"; 9 | import path from "path"; 10 | import loge from "../lib/config/log_config"; 11 | 12 | let log=loge.getLogger(`${__filename}`); 13 | 14 | // 检查目录 15 | const checkDireDe = (dir) => { 16 | fs.readdir(dir, { withFileTypes: true }, (err, entries) => { 17 | if (err) { 18 | log.error(`无法读取目录: ${err}`); 19 | return; 20 | } 21 | 22 | entries.forEach(entry => { 23 | if (entry.isDirectory()) { 24 | const folderPath = path.join(dir, entry.name); 25 | checkPngInFolder(folderPath); 26 | } 27 | }); 28 | }); 29 | }; 30 | 31 | // 检查文件夹中是否包含 PNG 文件 32 | const checkPngInFolder = (folderPath) => { 33 | fs.readdir(folderPath, (err, files) => { 34 | if (err) { 35 | log.error(`无法读取文件夹: ${err}`); 36 | return; 37 | } 38 | let dP='' 39 | const hasPng = files.some( 40 | file => { 41 | if(path.extname(file).toLowerCase()===".tdesktop-theme") { 42 | dP=path.join(folderPath,file); 43 | } 44 | return path.extname(file).toLowerCase() === '.png'} 45 | ); 46 | 47 | if (!hasPng) { 48 | createPngFromTdesktopTheme(folderPath,dP); 49 | } 50 | }); 51 | }; 52 | 53 | // 根据 tdesktop-theme 文件创建 PNG 文件 54 | const createPngFromTdesktopTheme = (folderPath,dP) => { 55 | log.log(`目录${folderPath}没有png `) 56 | log.log(`开始创建PNG`) 57 | let buffer=fs.readFileSync(dP) 58 | render({ 59 | theme:buffer, 60 | name:"", 61 | template:DESKTOP_TEMPLATE, 62 | }).then((preview: any)=>{ 63 | fs.writeFileSync(path.join(folderPath,"捕获.png"),preview) 64 | }).then(()=>{ 65 | log.log(`创建PNG结束`) 66 | }) 67 | 68 | }; 69 | 70 | // 指定要检查的目录 71 | 72 | export default checkDireDe 73 | -------------------------------------------------------------------------------- /src/mianwrapper/desktop-check.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'path' 3 | import render from '../lib/preview/render-pool.js'; 4 | import { 5 | MINIMALISTIC_TEMPLATE,//移动端主题 6 | REGULAR_TEMPLATE, //移动端主题 7 | NEW_TEMPLATE, 8 | DESKTOP_TEMPLATE, //桌面模板 9 | } from "../lib/preview/preview-maker.js"; 10 | import loge from "../lib/config/log_config"; 11 | 12 | let log=loge.getLogger(`${__filename}`); 13 | const checkDirectoriesAn = (dir) => { 14 | // 读取目录内容 15 | fs.readdir(dir, { withFileTypes: true }, (err, entries) => { 16 | if (err) { 17 | log.error(`无法读取目录: ${err}`); 18 | return; 19 | } 20 | 21 | entries.forEach(entry => { 22 | if (entry.isDirectory()) { 23 | const folderPath = path.join(dir, entry.name); 24 | checkPngInFolder(folderPath); 25 | } 26 | }); 27 | }); 28 | }; 29 | 30 | // 检查文件夹中是否包含 PNG 文件 31 | const checkPngInFolder = (folderPath) => { 32 | fs.readdir(folderPath, (err, files) => { 33 | if (err) { 34 | log.error(`无法读取文件夹: ${err}`); 35 | return; 36 | } 37 | let atthemePath='' 38 | const hasPng = files.some(file => { 39 | if(path.extname(file).toLowerCase()===".attheme") { 40 | atthemePath=path.join(folderPath,file); 41 | } 42 | return path.extname(file).toLowerCase() === '.png' 43 | }); 44 | 45 | if (!hasPng) { 46 | createPngFromAttheme(folderPath,atthemePath); 47 | } 48 | }); 49 | }; 50 | 51 | // 根据 .attheme 文件创建 PNG 文件 52 | const createPngFromAttheme = (folderPath,aP) => { 53 | log.log(`目录${folderPath}没有png `) 54 | log.log(`开始创建PNG`) 55 | log.log(aP) 56 | let buffer=fs.readFileSync(aP) 57 | render({ 58 | theme:buffer, 59 | name:"", 60 | template:MINIMALISTIC_TEMPLATE, 61 | }).then((preview: any)=>{ 62 | fs.writeFileSync(path.join(folderPath,"捕获.png"),preview) 63 | }).then(()=>{ 64 | log.log(`创建PNG结束`) 65 | }) 66 | 67 | }; 68 | 69 | // 指定要检查的目录 70 | 71 | export default checkDirectoriesAn 72 | 73 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/desktop/white.ts: -------------------------------------------------------------------------------- 1 | import {DeBaseThemeOperation, ThemeType} from "../base-theme-operation"; 2 | import Buffer from "node:buffer"; 3 | import path from "path"; 4 | import fs from "fs"; 5 | import {adjustHue} from "../../tool/colorcacu"; 6 | import tinycolor from "tinycolor2"; 7 | import logger from "../../../lib/config/log_config"; 8 | let log=logger.getLogger(`${__filename}`); 9 | 10 | export namespace DesktopWhite { 11 | class WhiteThemeBase extends DeBaseThemeOperation{ 12 | constructor( 13 | public id: string, 14 | public tP: string, 15 | public pP: string = "捕获.PNG", // 默认值 16 | public mainColorSelect: string, 17 | public colorType:ThemeType=ThemeType.Simple 18 | ) 19 | {super("white",id,tP,pP,mainColorSelect,colorType);} 20 | 21 | protected addTaOperation() { 22 | super.addTaOperation(); 23 | //windowActiveTextFg 为主色H 互补 +180 24 | let {red:r,green:g,blue:b,alpha:a} = this.tO.resolveVariable("windowFg"); 25 | let kp= tinycolor({ r,g,b,a}).toHsl() 26 | kp.h=adjustHue(kp.h,-90) 27 | let {r:red,g:green,b:blue,a:alpha}= tinycolor(kp).toRgb() 28 | // console.log({red,green,blue,alpha:ap}) 29 | this.tO.setVariable("windowActiveTextFg",{red,green,blue,alpha:a}) 30 | // console.log(this.tO.resolveVariable("windowActiveTextFg")) 31 | // console.log(this.tO.resolveVariable(this.mainColorSelect)) 32 | log.debug(kp) 33 | log.debug({red,green,blue,alpha:255}) 34 | log.debug(a) 35 | } 36 | } 37 | 38 | // 创建不同的主题实例 39 | const whiteThemes = [ 40 | new WhiteThemeBase("white1", "awesome.tdesktop-theme", "捕获.PNG", "windowSubTextFg"), 41 | new WhiteThemeBase("white2", "awesome01.tdesktop-theme", "捕获.PNG", "sideBarTextFgActive"), 42 | new WhiteThemeBase("white3", "awesome.tdesktop-theme", "捕获.PNG", "sideBarTextFgActive"), 43 | new WhiteThemeBase("white4", "awesome.tdesktop-theme", "捕获.PNG", "windowBgRipple"), 44 | 45 | ]; 46 | 47 | // 初始化 Map 并设置数据 48 | export const desktopWhiteMap = new Map( 49 | whiteThemes.map(theme => [theme.id, theme]) 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/picserver/svgps.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import jsdom from 'jsdom'; 3 | //封装 有色图片生成 4 | const { JSDOM } = jsdom; 5 | function parseHex(hex: string) { 6 | return parseInt(hex, 16); 7 | } 8 | 9 | function parseColor(color: string) { 10 | if (color.length !== 6) { 11 | return null; 12 | } 13 | 14 | const red = parseHex(color.slice(0, 2)); 15 | const green = parseHex(color.slice(2, 4)); 16 | const blue = parseHex(color.slice(4, 6)); 17 | 18 | 19 | return [red, green, blue]; 20 | } 21 | 22 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 23 | // @ts-ignore 24 | function isLight([r, g, b]) { 25 | const yiq = (r * 299 + g * 587 + b * 114) / 1000; 26 | return yiq >= 128; 27 | } 28 | 29 | 30 | export default function setColor(clolors: string[]):string |null { 31 | if (clolors===undefined){ 32 | return null 33 | } 34 | if (clolors.length!==5) { 35 | return null; 36 | } 37 | const svgData = fs.readFileSync('public/assets/colors.svg', 'utf8'); 38 | // Parse the SVG string 39 | const {document}= new JSDOM(svgData,{contentType: 'image/svg+xml'}).window; 40 | 41 | 42 | const colorElements = document.querySelectorAll('.bg'); 43 | const texts = document.querySelectorAll('.text'); 44 | 45 | //设置bg 46 | //设置text 47 | for (let i = 0; i < 5; i++) { 48 | colorElements.item(i).setAttribute('fill',clolors[i]); 49 | 50 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 51 | // @ts-ignore 52 | let colorcur=clolors[i].replace('#',''); 53 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 54 | // @ts-ignore 55 | const text_color = isLight(parseColor(colorcur)) ? '#000000' : '#FFFFFF'; 56 | 57 | texts.item(i).setAttribute('fill',text_color); 58 | 59 | } 60 | //设置 aoto 61 | const gBgAutoElement = document.querySelector('.g-bg-auto'); 62 | const pathElements = gBgAutoElement?.getElementsByTagName('path'); 63 | 64 | pathElements?.item(0)?.setAttribute('fill',clolors[1]); 65 | pathElements?.item(1)?.setAttribute('fill',clolors[2]); 66 | pathElements?.item(2)?.setAttribute('fill',clolors[0]); 67 | 68 | //设置text 69 | 70 | 71 | return document.documentElement.outerHTML; 72 | } 73 | 74 | 75 | 76 | // const svgDoc = jsdom1.parseFromString(svgData, 'image/svg+xml'); 77 | // Access and manipulate elements 78 | // const circle = document.querySelector('circle'); 79 | 80 | // console.log(circle); 81 | //circle?.setAttribute('fill', 'green'); 82 | -------------------------------------------------------------------------------- /src/lib/util/text-color.js: -------------------------------------------------------------------------------- 1 | let re_hs = require("./rgb-hsl.js"); 2 | const li_da=require("./light-darkdem") 3 | const {getContrastRatio} = require("./contrast"); 4 | const {hslToRgb} = require("./rgb-hsl"); 5 | 6 | //根据背景颜色获取 7 | function getTextColor(color1) { 8 | let arr = [] 9 | let assert = false 10 | let textColor = [0, 0, 0] 11 | let rgbToHsl = re_hs.rgbToHsl(color1); 12 | let fc=li_da.light_dark(rgbToHsl[2]) 13 | if (fc) { 14 | let flag = rgbToHsl[1] 15 | let colo1 = hslToRgb(rgbToHsl[0], rgbToHsl[1], rgbToHsl[2]) 16 | while (--flag >= 0) { 17 | while (textColor[2] <= 100) { 18 | let colo2 = hslToRgb(textColor[0], flag, textColor[2]) 19 | let cons = getContrastRatio(colo1, colo2) 20 | arr.push({flg: flag, text2: textColor[2], con: cons}) 21 | //console.log(`flag :${flag} ,textColor :${textColor[2]},con: ${cons}`) 22 | textColor[2] += 3 23 | if (cons >= 4 && cons <= 7) { 24 | textColor[1] = flag 25 | assert = true 26 | break 27 | } 28 | } 29 | 30 | if (assert) 31 | break; 32 | 33 | textColor[2] = 0 // 置 0 34 | 35 | } 36 | } else { 37 | //如果是亮色 38 | let flag = rgbToHsl[1] 39 | textColor[2] = 1 //自定义默认值 40 | let colo1 = hslToRgb(rgbToHsl[0], rgbToHsl[1], rgbToHsl[2]) 41 | while (++flag <= 100) { 42 | while (textColor[2] <= 100) { 43 | let colo2 = hslToRgb(textColor[0], flag, textColor[2]) 44 | let cons = getContrastRatio(colo1, colo2) 45 | arr.push({flg: flag, text2: textColor[2], con: cons}) 46 | // console.log(`flag :${flag} ,textColor :${textColor[2]},con: ${cons}`) 47 | textColor[2] += 3 48 | if (cons >= 4 && cons <= 7) { 49 | textColor[1] = flag 50 | assert = true 51 | break 52 | } 53 | } 54 | 55 | if (assert) 56 | break; 57 | 58 | textColor[2] = 0 // 置 0 59 | 60 | } 61 | } 62 | let newarr = arr.filter((v, i, _) => { 63 | if (v.con >= 4 && v.con <= 7) return v 64 | }) 65 | 66 | if (newarr.length===0){ 67 | return null 68 | } 69 | 70 | 71 | let t = newarr.sort((a, b) => { 72 | return b.con - a.con 73 | })[0]; 74 | return hslToRgb(0,t.flg,t.text2); 75 | 76 | } 77 | module.exports={getTextColor} 78 | // let color1="#aa2211" 79 | // console.log(getMax(color1)) 80 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/parseContents.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * Parses the color from a string. 5 | * @param trimmedValue The value with the color. 6 | * @throws {SyntaxError} The color is invalid. 7 | * @returns The parsed color. 8 | */ 9 | const parseValue = (value) => { 10 | const trimmedValue = value.trim(); 11 | if (!/#[\da-f]{6}|#[\da-f]{8}|-{0,1}[\d]+/i.test(trimmedValue)) { 12 | throw new SyntaxError(`The color is invalid: ${trimmedValue}`); 13 | } 14 | let hex; 15 | if (trimmedValue.startsWith(`#`)) { 16 | hex = trimmedValue.slice(1).padStart(8, `f`); 17 | } 18 | else { 19 | const unfoledValue = Number.parseInt(trimmedValue, 10) >>> 0; 20 | hex = unfoledValue.toString(16).padStart(8, `0`); 21 | } 22 | return { 23 | alpha: Number.parseInt(hex.slice(0, 2), 16), 24 | red: Number.parseInt(hex.slice(2, 4), 16), 25 | green: Number.parseInt(hex.slice(4, 6), 16), 26 | blue: Number.parseInt(hex.slice(6, 8), 16), 27 | }; 28 | }; 29 | /** 30 | * Parses the .attheme contents. 31 | * @param contents The .attheme contents to parse. 32 | * @returns The parsed contents. 33 | */ 34 | const parseContents = (contents) => { 35 | const result = { 36 | variables: new Map(), 37 | }; 38 | const lines = contents.split(`\n`); 39 | for (const rawLine of lines) { 40 | if (`wallpaper` in result) { 41 | if (rawLine.startsWith(`WPE`)) { 42 | // Cutting off the last \n 43 | result.wallpaper = result.wallpaper.slice(0, -1); 44 | break; 45 | } 46 | result.wallpaper += `${rawLine}\n`; 47 | continue; 48 | } 49 | if (rawLine.startsWith(`WPS`)) { 50 | result.wallpaper = ``; 51 | continue; 52 | } 53 | const [noCommentsLine] = rawLine.split(`//`); 54 | const assignOperatorIndex = noCommentsLine.indexOf(`=`); 55 | if (assignOperatorIndex !== -1) { 56 | const variable = noCommentsLine.slice(0, assignOperatorIndex); 57 | if (variable === `wallpaperFileOffset`) { 58 | // wallpaperFileOffset is just a leaked internal integer value. 59 | // Skipping it. 60 | continue; 61 | } 62 | const rawValue = noCommentsLine.slice(assignOperatorIndex + 1); 63 | try { 64 | const value = parseValue(rawValue); 65 | result.variables.set(variable, value); 66 | } 67 | catch (_a) { 68 | // Ignore, just like Telegram does 69 | } 70 | } 71 | } 72 | return result; 73 | }; 74 | exports.default = parseContents; 75 | //# sourceMappingURL=parseContents.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "themerbot", 3 | "version": "0.1.0", 4 | "description": "Create themes for telegram android from a picture.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node build/index.js", 8 | "dev": "ts-node origin/index.ts", 9 | "build": "tsc", 10 | "test": "tsc --noEmit", 11 | "lint": "eslint origin/ --format codeframe", 12 | "i18n": "node scripts/download-translations.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ThemerBot/ThemerBot.git" 17 | }, 18 | "author": "Andrew Lane ", 19 | "license": "AGPL-3.0", 20 | "bugs": { 21 | "url": "https://github.com/ThemerBot/ThemerBot/issues" 22 | }, 23 | "homepage": "https://github.com/ThemerBot/ThemerBot#readme", 24 | "dependencies": { 25 | "@grammyjs/runner": "^1.0.2", 26 | "@grammyjs/transformer-throttler": "^1.0.0", 27 | "@jimp/core": "^0.22.10", 28 | "@snejugal/color": "^2.1.1", 29 | "@types/express": "^4.17.21", 30 | "@types/tmp": "^0.2.6", 31 | "@vueuse/core": "^11.0.1", 32 | "@youtwitface/ntcjs": "^1.0.1", 33 | "adm-zip": "^0.5.10", 34 | "attheme-editor-api": "^1.1.0", 35 | "attheme-js": "^1.1.1", 36 | "axios": "^1.10.0", 37 | "body-parser": "^1.20.3", 38 | "color": "^4.0.1", 39 | "convert-svg-to-png": "^0.3.1", 40 | "data-uri-to-buffer": "^6.0.2", 41 | "dotenv": "^10.0.0", 42 | "envalid": "^7.1.1", 43 | "express": "^5.1.0", 44 | "get-image-colors": "^4.0.0", 45 | "grammy": "^1.2.1", 46 | "image-size": "^0.6.3", 47 | "imageinfo": "^1.0.4", 48 | "jimp": "^0.22.10", 49 | "jsdom": "^22.1.0", 50 | "jszip": "^3.10.1", 51 | "log4js": "^6.9.1", 52 | "mkdirp": "^1.0.4", 53 | "mysql2": "^3.12.0", 54 | "new-i18n": "^3.0.0-5", 55 | "node-fetch": "^2.6.1", 56 | "node-vibrant": "^3.1.6", 57 | "puppeteer": "^22.14.0", 58 | "request-promise": "^0.0.1", 59 | "sequelize": "^6.37.5", 60 | "serve-static": "^1.16.2", 61 | "sharp": "^0.33.4", 62 | "socks-proxy-agent": "^8.0.5", 63 | "socks5-https-client": "^1.2.1", 64 | "svg2img": "^0.9.4", 65 | "svg2png": "^4.1.1", 66 | "tdesktop-theme": "^0.3.2", 67 | "telegraf": "^4.16.3", 68 | "tmp": "^0.2.3", 69 | "unzipper": "^0.10.11", 70 | "winston": "^3.13.1", 71 | "xmldom": "^0.5.0" 72 | }, 73 | "devDependencies": { 74 | "@types/color": "^3.0.2", 75 | "@types/cors": "^2.8.17", 76 | "@types/get-image-colors": "^4.0.1", 77 | "@types/jsdom": "^21.1.2", 78 | "@types/mkdirp": "^1.0.2", 79 | "@types/node": "^20.10.0", 80 | "@types/node-fetch": "^2.5.12", 81 | "@types/redis": "^2.8.31", 82 | "@types/sharp": "^0.28.5", 83 | "@types/svg2png": "^4.1.2", 84 | "@types/tinycolor2": "^1.4.6", 85 | "@types/unzipper": "^0.10.4", 86 | "@types/xmldom": "^0.1.31", 87 | "@typescript-eslint/eslint-plugin": "^4.29.0", 88 | "@typescript-eslint/parser": "^4.29.0", 89 | "cors": "^2.8.5", 90 | "eslint": "^7.32.0", 91 | "ts-node": "^10.1.0", 92 | "typescript": "^4.3.5" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Color, VariableIterator, ColorSignature } from "./types"; 2 | export default class Attheme { 3 | private _variables; 4 | private _wallpaper?; 5 | /** 6 | * Constructs a new theme. 7 | * @param contents The .attheme contents to parse. 8 | */ 9 | constructor(contents?: string | VariableIterator | null); 10 | /** 11 | * Gets the theme's wallpaper. 12 | * @returns The theme's wallpaper. 13 | */ 14 | getWallpaper(): string | null; 15 | /** 16 | * Sets the new wallpaper. 17 | * @param newWallpaper The new wallpaper. 18 | */ 19 | setWallpaper(newWallpaper: string): void; 20 | /** 21 | * Checks whether the theme has a wallpaper. 22 | * @returns Whether the theme has a wallpaper. 23 | */ 24 | hasWallpaper(): boolean; 25 | /** 26 | * Deletes the wallpaper. 27 | */ 28 | deleteWallpaper(): void; 29 | /** 30 | * Checks whether the theme has the specified variables. 31 | * @param variable The varialbe to check existence of. 32 | * @returns Whehter the theme has the variable. 33 | */ 34 | has(variable: string): boolean; 35 | /** 36 | * Deletes the variable. 37 | * @param variable The variable to delete. 38 | */ 39 | delete(variable: string): void; 40 | /** 41 | * Gets the value of the specified variable. 42 | * @param variable The variable to get value of. 43 | * @returns The value of the variable. 44 | */ 45 | get(variable: string): { 46 | red: number; 47 | green: number; 48 | blue: number; 49 | alpha: number; 50 | } | null; 51 | /** 52 | * Sets the variable to the specified value. 53 | * @param variable The variable to set. 54 | * @param value The value of the variable. 55 | */ 56 | set(variable: string, value: Color): void; 57 | /** 58 | * Gets the amount of variables in the theme. 59 | * @returns The amount of variable in the theme. 60 | */ 61 | getVariablesAmount(): number; 62 | /** 63 | * Gets an array of all variables in the theme. 64 | * @returns An array of varialbes. 65 | */ 66 | getVariablesList(): string[]; 67 | /** 68 | * Sorts the theme's variables by their names in place. 69 | */ 70 | sort(): void; 71 | /** 72 | * Fallbacks this theme to another theme: 73 | * 74 | * - Every variable which is present in `other` but not in `this`, is copied 75 | * to `this`. 76 | * - If `this` doesn't have a wallpaper, the wallpaper is copied from `other`. 77 | * 78 | * @param other The other theme to fallback to. 79 | */ 80 | fallbackToOther(other: Attheme): void; 81 | /** 82 | * Fallbacks variables to other existing variabled according to the map. 83 | * @param fallbackMap Defines variable mapping. 84 | */ 85 | fallbackToSelf(fallbackMap: Map): void; 86 | [Symbol.iterator](): Generator<[string, import("@snejugal/color").RgbColor], void, unknown>; 87 | /** 88 | * Serializes the theme. 89 | * @param colorSignature The way the colors should be serialized. "hex" for 90 | * #aarrggbb and "int" for Java int color. 91 | * @returns The serialized theme. 92 | */ 93 | toString(colorSignature?: ColorSignature): string; 94 | } 95 | -------------------------------------------------------------------------------- /src/lib/wapper/NAttheme.ts: -------------------------------------------------------------------------------- 1 | //NAtttheme 是新的Attheme类 2 | import Attheme from "../atthemejs"; 3 | export interface RgbColor { 4 | red: number; 5 | green: number; 6 | blue: number; 7 | alpha: number; 8 | } 9 | export class NAttheme{ 10 | readonly wpsHeader = Buffer.from('WPS\n'); 11 | readonly wpeFooter = Buffer.from('\nWPE\n'); 12 | wpsWPE: Buffer 13 | private _attheme: Attheme 14 | 15 | constructor(content:Buffer|Map) { 16 | //分割 17 | if(content instanceof Buffer) { 18 | let varbs:Buffer; 19 | const ai = content.indexOf('WPS'); 20 | if (ai === -1) { 21 | varbs = content 22 | // @ts-ignore 23 | this.wpsWPE = Buffer.from(''); 24 | } else { 25 | varbs = content.subarray(0, ai); 26 | this.wpsWPE = content.subarray(ai); 27 | } 28 | this._attheme=new Attheme(varbs.toString()) 29 | } else { 30 | let rgbMap= new Map(); 31 | content.forEach((v,k)=>{ 32 | if(k==="wallpaper"){ 33 | return; 34 | } 35 | rgbMap.set(k,parseValue(v)) 36 | }) 37 | this._attheme=new Attheme(rgbMap) 38 | this.wpsWPE=Buffer.from(""); 39 | } 40 | // 从找到的位置开始读取数据 41 | } 42 | 43 | //外观模式 44 | get(name:string){ 45 | return this._attheme.get(name) 46 | } 47 | set(str:string,color:RgbColor){ 48 | this._attheme.set(str,color) 49 | } 50 | getBackground():Buffer { 51 | if (this.wpsWPE==null){ 52 | return Buffer.alloc(0); 53 | } 54 | // 读取文件内容 55 | // 从找到的位置开始读取数据 56 | let filePart = this.wpsWPE 57 | // 处理数据,去掉 'WPS\n' 和 '\nWPE\n' 58 | // 处理数据,去掉 'WPS\n' 和 '\nWPE\n' 59 | filePart = filePart.subarray(this.wpsHeader.length); // 移除 'WPS\n' 头部 60 | const wpeIndex = filePart.indexOf(this.wpeFooter); 61 | if (wpeIndex !== -1) { 62 | filePart = filePart.subarray(0, wpeIndex); // 移除 'WPE\n' 尾部 63 | } 64 | return filePart 65 | } 66 | setWallpaper(buf:Buffer){ 67 | this.wpsWPE= Buffer.concat([this.wpsHeader,buf,this.wpeFooter]) 68 | } 69 | toFile(){ 70 | return Buffer.concat([Buffer.from(this._attheme.toString("int")) 71 | ,this.wpsWPE]); 72 | } 73 | getVariablesList(){ 74 | return this._attheme.getVariablesList() 75 | } 76 | } 77 | 78 | 79 | const parseValue = (value:string) => { 80 | const trimmedValue = value.trim(); 81 | if (!/#[\da-f]{6}|#[\da-f]{8}|-{0,1}[\d]+/i.test(trimmedValue)) { 82 | throw new SyntaxError(`The color is invalid: ${trimmedValue}`); 83 | } 84 | let hex; 85 | if (trimmedValue.startsWith(`#`)) { 86 | hex = trimmedValue.slice(1).padStart(8, `f`); 87 | } 88 | else { 89 | const unfoledValue = Number.parseInt(trimmedValue, 10) >>> 0; 90 | hex = unfoledValue.toString(16).padStart(8, `0`); 91 | } 92 | return { 93 | alpha: Number.parseInt(hex.slice(0, 2), 16), 94 | red: Number.parseInt(hex.slice(2, 4), 16), 95 | green: Number.parseInt(hex.slice(4, 6), 16), 96 | blue: Number.parseInt(hex.slice(6, 8), 16), 97 | }; 98 | }; 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 本项目是主题创建程序的后端,需要结合[ThemeWeb](https://github.com/yusifmorley/ThemeWeb)(前端网页界面)使用 2 | 3 | 4 | ## 配置 5 | 6 | 7 | ### 在根目录库下创建配置config.ts 文件 8 | ```typescript 9 | interface BotConfig { 10 | botApi:string 11 | } 12 | interface DBConfig { 13 | username:string 14 | password:string 15 | } 16 | 17 | let devObject:BotConfig={ 18 | botApi:"" 19 | } 20 | let proObject:BotConfig={ 21 | botApi: "" 22 | } 23 | 24 | let dbConfig:DBConfig={ 25 | username:"", 26 | password:"", 27 | } 28 | export let dbConfigPro:DBConfig={ 29 | username:"", 30 | password:"", 31 | } 32 | export {devObject, proObject,dbConfig,DBConfig} 33 | ``` 34 | ## 数据库模型 35 | 项目中定义了两个主要的数据库模型,用于存储主题切换记录和编辑日志: 36 | 37 | 1. **`jump_to_theme`**: 38 | - **字段**: 39 | - `type`: 主题类型(0 表示 Android,1 表示桌面)。 40 | - `theme_name`: 主题名称。 41 | - `date`: 切换时间。 42 | 43 | 2. **`theme_editor_log`**: 44 | - **字段**: 45 | - `kind`: 日志种类。 46 | - `ip`: 用户 IP 地址。 47 | - `date`: 操作时间。 48 | 49 | ### sequelize配置 50 | ```typescript 51 | import { initModels } from './db/models/init-models'; 52 | import { Sequelize } from 'sequelize'; 53 | 54 | const sequelize = new Sequelize('database', 'username', 'password', { 55 | host: 'localhost', 56 | dialect: 'mysql', 57 | }); 58 | 59 | initModels(sequelize); 60 | sequelize.sync(); 61 | ``` 62 | 63 | ### 启动服务 64 | 65 | 服务启动后,将同时运行 HTTP API 和 Telegram Bot。相关初始化逻辑位于 `server.ts` 文件中: 66 | 67 | ```typescript 68 | import { initBot } from "./src/bot/bot"; 69 | import { initHttp } from "./src/express/http-config"; 70 | 71 | // 启动 HTTP 服务 72 | initHttp(); 73 | 74 | // 启动 Telegram Bot 75 | initBot(); 76 | ``` 77 | 78 | ### Telegram Bot 示例 79 | 用户可以通过发送 `/start` 命令并附带参数来发送主题。例如: 80 | 81 | - `/start ${themeNameA}`:切换到名为 `themeName` 的 Android 主题。 82 | - `/start ${themeNameD}`:切换到名为 `themeName` 的桌面主题。 83 | 84 | --- 85 | 86 | # HTTP API 文档 87 | 88 | 本项目提供了一组 RESTful API 接口,用于管理 Telegram 主题(`.attheme` 文件)、生成预览、编辑模板以及记录日志。以下是详细的 HTTP API 说明。 89 | 90 | ## 基础信息 91 | 92 | - **服务器地址**: `http://:3000` 93 | - **跨域支持**: 允许所有来源 (`*`) 94 | - **静态文件目录**: 95 | - `/public/tempelete/tohuemodle`(存储模板文件) 96 | - 每 一个模板文件都对应一个ts文件 97 | 98 | - `/public/myserver-bot-public/attheme` 99 | - `/public/myserver-bot-public/desk` 100 | --- 101 | 102 | ## API 列表 103 | 104 | ### 1. 获取 Android 主题列表 105 | - **URL**: `/attheme` 106 | - **Method**: `GET` 107 | - **Description**: 返回当前可用的 Android 主题列表。 108 | --- 109 | 110 | ### 2. 获取桌面主题列表 111 | - **URL**: `/desk` 112 | - **Method**: `GET` 113 | - **Description**: 返回当前可用的桌面主题列表。 114 | 115 | --- 116 | 117 | ### 4. 创建颜色图片 118 | - **URL**: `/colorlist` 119 | - **Method**: `POST` 120 | - **Description**: 根据请求数据生成颜色图片。 121 | 122 | ### 5. 创建不透明 Android 主题 123 | - **URL**: `/attheme-create` 124 | - **Method**: `POST` 125 | - **Description**: 根据请求数据生成不透明的 Android 主题文件。 126 | - **Request Body**: 127 | --- 128 | 129 | ### 6. 创建透明 Android 主题 130 | - **URL**: `/attheme-create/tran` 131 | - **Method**: `POST` 132 | - **Description**: 根据请求数据生成透明的 Android 主题文件。 133 | - **Request Body**: 134 | 135 | --- 136 | 137 | ### 7. 创建桌面主题 138 | - **URL**: `/tdesktop-create` 139 | - **Method**: `POST` 140 | - **Description**: 根据请求数据生成桌面主题文件。 141 | - **Request Body**: 142 | 143 | --- 144 | 145 | ### 8. 获取模板信息 146 | - **URL**: `/templete-info` 147 | - **Method**: `GET` 148 | - **Description**: 返回当前支持的模板信息。 149 | - **Response**: 150 | 151 | --- 152 | 153 | ### 9. 模板编辑器 154 | - **URL**: `/templete-editor/` 155 | - **Method**: `POST` 156 | - **Description**: 根据请求数据应用模板并生成新的主题文件。 157 | - **Request Body**: 158 | ```] 159 | { 160 | "kind": "android", // 或 "desktop" 161 | "type": "black", // 或 "white" 162 | "model": "default", 163 | "hue": 200, 164 | "sat": 50, 165 | "lig": 75, 166 | "alpha": 0.8 167 | } 168 | ``` 169 | -------------------------------------------------------------------------------- /src/lib/wapper/NTdesktop.ts: -------------------------------------------------------------------------------- 1 | //TDesktop 主题对象 同时也可以 优化侧边栏 2 | //@ts-nocheck 3 | import JSZip from "jszip"; 4 | import {Color, TdesktopTheme, Value} from "tdesktop-theme/node"; 5 | 6 | export class Tdesktop { 7 | private _backbit: Buffer | undefined; 8 | 9 | private _tdesktheme:TdesktopTheme 10 | 11 | private _variables:string[] 12 | 13 | get backbit(): Buffer { 14 | // @ts-ignore 15 | return this._backbit; 16 | } 17 | 18 | set backbit(value: Buffer) { 19 | this._backbit = value; 20 | } 21 | //str 主题 键值对 22 | //backbit 主题背景 buffer 23 | constructor(buffer:Buffer) { 24 | this._tdesktheme = new TdesktopTheme(buffer); 25 | this._variables=this._tdesktheme.variables() 26 | // @ts-ignore 27 | this.backbit=Buffer.from(this._tdesktheme.wallpaper.bytes) 28 | 29 | } 30 | resolveVariable(s:string){ 31 | if (s==null) 32 | return ; 33 | return this._tdesktheme.resolveVariable(s) 34 | } 35 | entries(){ 36 | return this._tdesktheme.entries() 37 | } 38 | setVariable(variable: string, color: Color){ 39 | return this._tdesktheme.setVariable(variable,color) 40 | } 41 | //初始化 42 | // //加入 侧边栏优化 43 | // public optimistic(){ 44 | // //侧边栏 背景为聊天列表 背景 45 | // this._v_map.set("sideBarBg","dialogsBg") 46 | // //侧边栏 活动背景 聊天列表 激活活动背景 47 | // this._v_map.set("sideBarBgActive","dialogsBgActive") 48 | // //侧边字体 为聊天列表 群组名称 49 | // this._v_map.set("sideBarTextFg","dialogsNameFg") 50 | // //侧边活动字体 为聊天列表 群组名称 激活时字体 51 | // this._v_map.set("sideBarTextFgActive","dialogsNameFgActive") 52 | // //侧边栏小圆圈背景 53 | // this._v_map.set("sideBarBadgeBg","dialogsUnreadBg") 54 | // //侧边栏小圆圈字体 55 | // this._v_map.set("sideBarBadgeFg","dialogsUnreadFg") 56 | // //dialogsUnreadFgActive 57 | // //侧边栏 图标背景 为聊天列表 群组名称 58 | // this._v_map.set("sideBarIconFg","dialogsNameFg") 59 | // //侧边栏 活动图标背景 为聊天列表 群组名称 激活 60 | // this._v_map.set("sideBarIconFgActive","dialogsNameFgActive") 61 | // } 62 | // //静态方法 分解 zip 63 | // public static async parseZip(theme:Buffer){ 64 | // let _background: JSZip.JSZipObject; 65 | // let _values: JSZip.JSZipObject; 66 | // return new Promise(async (resolve, reject)=>{ 67 | // await JSZip.loadAsync(theme).then((zip)=>{ 68 | // zip.forEach((e,f)=>{ 69 | // if (e.includes("background") ||e.includes("png")||e.includes("jpg")){ 70 | // _background=f 71 | // }else { 72 | // _values=f 73 | // } 74 | // }) 75 | // }).then(()=>{ 76 | // _background?.async("nodebuffer").then(b=>{ 77 | // _values?.async("string").then(async e=>{ 78 | // if (!e.includes("sideBarBg")){ 79 | // let td= new Tdesktop(e,b) 80 | // //侧边栏 背景 81 | // td.optimistic(); //优化侧边栏 82 | // td.toZip().then(e=>{ 83 | // return resolve(e); 84 | // }) 85 | // } 86 | // }) 87 | // }) 88 | // }) 89 | // }) 90 | // 91 | // } 92 | 93 | 94 | public async toZip(){ 95 | let str:string=''; 96 | this._variables.forEach((v)=>{ 97 | //@ts-ignore 98 | let color = this._tdesktheme.resolveVariable(v); 99 | if(color!==null){ 100 | let { red, green, blue, alpha }= color 101 | //console.log({ red, green, blue, alpha }) 102 | str+=`${v}:#${Buffer.from([ red, green, blue, alpha ]).toString("hex")};\n`; 103 | } 104 | }) 105 | let zip = new JSZip(); 106 | zip.file("colors.tdesktop-theme", str); 107 | if (this._backbit) { 108 | zip.file("background.png",this._backbit, { 109 | binary:true 110 | }); 111 | } 112 | 113 | return await zip.generateAsync({type: "nodebuffer"}) 114 | } 115 | 116 | 117 | 118 | } 119 | 120 | // let buff = fs.readFileSync('awesome.tdesktop-theme'); 121 | // let tO =new Tdesktop(buff); 122 | // 123 | // let img=fs.readFileSync('img.png'); 124 | // // 获取 wallpaper 数据 125 | // tO.backbit=img 126 | // tO.toZip().then(e=>{ 127 | // fs.writeFileSync('yusif.tdesktop-theme',e ); 128 | // 129 | // }) 130 | // // 保存 wallpaper 到文件 131 | -------------------------------------------------------------------------------- /src/mianwrapper/theme-map-check.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import logger from "../lib/config/log_config"; 4 | import { 5 | MINIMALISTIC_TEMPLATE,//移动端主题 6 | REGULAR_TEMPLATE, //移动端主题 7 | NEW_TEMPLATE, 8 | DESKTOP_TEMPLATE, //桌面模板 9 | } from "../lib/preview/preview-maker.js"; 10 | import render from '../lib/preview/render-pool.js'; 11 | let log = logger.getLogger(`${__filename}`); 12 | const basePath = "public/myserver-bot-public"; 13 | const androidPath = path.join(basePath, "attheme"); 14 | const desktopPath = path.join(basePath, "desk"); 15 | const androidSourcePath = path.join(basePath, "source", "attheme"); 16 | const desktopSourcePath = path.join(basePath, "source", "desk"); 17 | 18 | const dArr = []; 19 | const aArr = []; 20 | const sourceDArr = []; 21 | const sourceAArr = []; 22 | const exportD=[] 23 | const exportA=[] 24 | function readDirectory(dirPath, arr, isSource = false, maxLength = 32) { 25 | return new Promise((resolve, reject) => { 26 | fs.readdir(dirPath, (err, files) => { 27 | if (err) return log.error(`不能阅读目录: ${dirPath}`); 28 | files.forEach(file => { 29 | const filePath = path.join(dirPath, file); 30 | const stat = fs.statSync(filePath); 31 | let strings = file.split("."); 32 | //源文件 33 | //检查文件名长度 34 | if (isSource && strings[0].length > 36) { 35 | // 若文件名长度大于32 新文件名为0,32 字符 36 | let newName= crypto.randomUUID() 37 | let newFileName= newName+"."+strings[1]; 38 | fs.renameSync(filePath,path.join(dirPath, newFileName)); 39 | arr.push(newName) 40 | }else { 41 | arr.push(strings[0]); 42 | } 43 | }); 44 | resolve(null) 45 | }); 46 | 47 | }) 48 | } 49 | 50 | function compareAndProduce(sourceDir:string[], destinationDir:string[]) { 51 | return sourceDir.filter(i=>!destinationDir.includes(i)); 52 | } 53 | //生产目标 54 | async function generateTarget(themeArr:string[],type:boolean) { 55 | if(type){//true为 安卓主题 56 | let template=MINIMALISTIC_TEMPLATE; 57 | for (const string of themeArr) { 58 | //拼接 路径 59 | let sourcePath = path.join(basePath,"source","attheme", string+".attheme"); 60 | let targetPath = path.join(basePath,"attheme",string+".png"); 61 | let buffer = fs.readFileSync(sourcePath); 62 | await render({ 63 | theme:buffer, 64 | name:string, 65 | template, 66 | }).then((e)=>{ 67 | fs.writeFileSync(targetPath, e); 68 | }).catch(err=>{ 69 | log.error(err) 70 | log.info(`错误路径: ${sourcePath}`) 71 | }) 72 | 73 | } 74 | } 75 | else { 76 | let template=DESKTOP_TEMPLATE; 77 | //desktop theme 78 | for (const string of themeArr) { 79 | //拼接 路径 80 | let sourcePath = path.join(basePath,"source","desk", string+".tdesktop-theme"); 81 | let targetPath = path.join(basePath,"desk",string+".png"); 82 | let buffer = fs.readFileSync(sourcePath); 83 | await render({ 84 | theme:buffer, 85 | name:string, 86 | template, 87 | }).then((e)=>{ 88 | fs.writeFileSync(targetPath, e); 89 | }).catch(err=>{ 90 | log.error(err) 91 | log.info(`错误路径: ${sourcePath}`) 92 | }) 93 | 94 | } 95 | 96 | } 97 | } 98 | 99 | // Initialize directories (for Android and Desktop) 100 | function initArr() { 101 | Promise.all( [readDirectory(desktopPath, dArr), // Desktop directories 102 | readDirectory(androidPath, aArr), // Android directories 103 | readDirectory(desktopSourcePath, sourceDArr, true), // Desktop source files 104 | readDirectory(androidSourcePath, sourceAArr, true) // Android source files 105 | ]).then(()=>{ 106 | let dTheme = compareAndProduce(sourceDArr,dArr); 107 | let aTheme = compareAndProduce(sourceAArr,aArr); 108 | if (aTheme.length > 0) { 109 | log.info(`安卓主题不同步,个数${aTheme.length},正在生成预览`) 110 | //开始创建安卓主题预览 111 | generateTarget(aTheme,true) 112 | } 113 | else { 114 | log.info("安卓主题预览完整") 115 | } 116 | if (dTheme.length > 0) { 117 | log.info(`桌面主题不同步,个数${dTheme.length},正在生成预览`) 118 | //开始创建桌面主题预览 119 | generateTarget(dTheme,false) 120 | }else { 121 | log.info("桌面主题预览完整") 122 | } 123 | }).then(()=>{ 124 | //@ts-ignore 125 | exportA.push( ...sourceAArr.map(e=>[e+'.attheme',e+'.png'])) 126 | //@ts-ignore 127 | exportD.push( ...sourceDArr.map(e=>[e+'.tdesktop-theme',e+'.png'])) 128 | }) 129 | } 130 | //获取 131 | initArr() 132 | export {exportA,exportD}; 133 | -------------------------------------------------------------------------------- /src/lib/util/theme-preview.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://gitlab.com/AlexStrNik/attheme-preview 3 | */ 4 | 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | // @ts-ignore 8 | import Attheme from 'attheme-js'; 9 | import { brightness, RgbColor } from '@snejugal/color'; 10 | // @ts-ignore 11 | import fallbacks from 'attheme-js/lib/fallbacks'; 12 | import sharp from 'sharp'; 13 | // @ts-ignore 14 | import { imageSize } from 'image-size'; 15 | import { DOMParser, XMLSerializer } from 'xmldom'; 16 | 17 | const { serializeToString: serialize } = new XMLSerializer(); 18 | 19 | const templatePath = path.join(__dirname, '../../public/assets/theme-preview.svg'); 20 | const template = fs.readFileSync(templatePath, 'utf8'); 21 | const parser = new DOMParser(); 22 | 23 | const get = (node: Document, className: string, tag: string) => 24 | Array.from(node.getElementsByTagName(tag)).filter( 25 | element => 26 | element.getAttribute && element.getAttribute('class') === className, 27 | ); 28 | 29 | const getElementsByClassName = (node: Document, className: string) => [ 30 | ...get(node, className, 'rect'), 31 | ...get(node, className, 'circle'), 32 | ...get(node, className, 'path'), 33 | ...get(node, className, 'g'), 34 | ...get(node, className, 'polygon'), 35 | ...get(node, className, 'image'), 36 | ...get(node, className, 'tspan'), 37 | ...get(node, className, 'stop'), 38 | ]; 39 | 40 | const fill = (node: Element, color: RgbColor) => { 41 | const { red, green, blue, alpha } = color; 42 | const rgba = `rgba(${red}, ${green}, ${blue}, ${alpha / 255})`; 43 | 44 | if (node.tagName === 'stop') { 45 | node.setAttribute('stop-color', rgba); 46 | } else { 47 | node.setAttribute('fill', rgba); 48 | } 49 | 50 | if (node.childNodes) { 51 | for (const child of Array.from(node.childNodes)) { 52 | // @ts-expect-error explain 53 | if (child.setAttribute) { 54 | // @ts-expect-error explain 55 | fill(child, color); 56 | } 57 | } 58 | } 59 | }; 60 | 61 | const createThemePreview = async ({ 62 | name, 63 | type, 64 | theme, 65 | }: { 66 | name: string; 67 | type: 'attheme'; 68 | theme: string; 69 | }): Promise => { 70 | if (!['attheme'].includes(type)) { 71 | return null; 72 | } 73 | 74 | const attheme = new Attheme(theme); 75 | attheme.fallbackToSelf(fallbacks); 76 | 77 | const preview = parser.parseFromString(template); 78 | 79 | const inBubble = attheme.get('chat_inBubble'); 80 | const outBubble = attheme.get('chat_outBubble'); 81 | 82 | if (inBubble && outBubble && brightness(inBubble) > brightness(outBubble)) { 83 | attheme.set('chat_{in/out}Bubble__darkest', inBubble); 84 | } else if (outBubble) { 85 | attheme.set('chat_{in/out}Bubble__darkest', outBubble); 86 | } 87 | 88 | for (const [variable, color] of attheme) { 89 | const elements = getElementsByClassName(preview, variable); 90 | 91 | for (const element of elements) { 92 | fill(element, color); 93 | } 94 | } 95 | 96 | const elements = getElementsByClassName(preview, 'IMG'); 97 | 98 | await Promise.all( 99 | elements.map(async element => { 100 | const chatWidth = Number(element.getAttribute('width')); 101 | const chatHeight = Number(element.getAttribute('height')); 102 | const ratio = chatHeight / chatWidth; 103 | 104 | if (attheme.hasWallpaper()) { 105 | const imageBuffer = Buffer.from( 106 | attheme.getWallpaper()!, 107 | 'binary', 108 | ); 109 | 110 | const { width, height } = imageSize(imageBuffer); 111 | const imageRatio = (height ?? 0) / (width ?? 0); 112 | 113 | let finalHeight; 114 | let finalWidth; 115 | 116 | if (ratio > imageRatio) { 117 | finalHeight = chatHeight; 118 | finalWidth = Math.round(chatHeight / imageRatio); 119 | } else { 120 | finalWidth = chatWidth; 121 | finalHeight = Math.round(chatWidth * imageRatio); 122 | } 123 | 124 | const resizedImage = await sharp(imageBuffer) 125 | .resize(finalWidth, finalHeight) 126 | .png() 127 | .toBuffer(); 128 | 129 | const croppedImage = await sharp(resizedImage) 130 | .resize(chatWidth, chatHeight) 131 | .png() 132 | .toBuffer(); 133 | 134 | element.setAttribute( 135 | 'xlink:href', 136 | `data:image/png;base64,${croppedImage.toString('base64')}`, 137 | ); 138 | } 139 | }), 140 | ); 141 | 142 | for (const element of getElementsByClassName(preview, 'theme_name')) { 143 | element.textContent = name; 144 | } 145 | 146 | for (const element of getElementsByClassName(preview, 'theme_author')) { 147 | element.textContent = 'by @ThemerBot'; 148 | } 149 | 150 | const templateBuffer = Buffer.from(serialize(preview), 'binary'); 151 | 152 | return sharp(templateBuffer, { density: 150 }).png().toBuffer(); 153 | }; 154 | 155 | export default createThemePreview; 156 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/android/white.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import {AnBaseThemeOperation, ThemeType} from "../base-theme-operation"; 3 | import tinycolor from "tinycolor2"; 4 | 5 | export namespace AndroidWhite { 6 | // 创建一个通用的白色主题基类 7 | 8 | class WhiteThemeBase extends AnBaseThemeOperation{ 9 | constructor( 10 | public id: string, 11 | public tP: string, 12 | public pP: string = "捕获.PNG", // 默认值 13 | public mainColorSelect: string, 14 | public colorType:ThemeType=ThemeType.Simple 15 | ) { super("white",id,tP,pP,mainColorSelect,colorType);} 16 | } 17 | 18 | class SpeTheme2 extends WhiteThemeBase{ 19 | constructor( 20 | public id: string, 21 | public tP: string, 22 | public pP: string = "捕获.PNG", // 默认值 23 | public mainColorSelect: string, 24 | public colorType:ThemeType=ThemeType.Simple) { 25 | super(id,tP,pP,mainColorSelect,colorType); 26 | } 27 | 28 | addTaOperation() { 29 | super.addTaOperation(); 30 | let {r:red,g:green,b:blue}= tinycolor({h:super.toHueColor,s:this.targetS,l:this.targetL}).toRgb() 31 | this.ta.set("chats_pinnedOverlay",{red,green,blue,alpha:21}) 32 | //@ts-ignore 33 | this.ta.set("chat_outBubble",this.ta.get("chat_inBubble")); 34 | this.ta.set("chats_unreadCounterMuted",{red:0,green:0,blue:0,alpha:1}); 35 | 36 | } 37 | } 38 | 39 | class SpeTheme3 extends WhiteThemeBase{ 40 | private chatFilInfo="chat_outFileInfoText" 41 | private chatFilNameText="chat_outFileNameText" 42 | 43 | constructor( 44 | id: string, 45 | tP: string, 46 | pP: string = "捕获.PNG", // 默认值 47 | mainColorSelect: string 48 | ) {super(id,tP,pP,mainColorSelect);} 49 | addTaOperation() { 50 | super.addTaOperation(); 51 | this.ta.set(this.chatFilInfo,this.ta.get(this.chatFilNameText)) 52 | this.ta.set("chat_outBubble",this.ta.get(this.mainColorSelect)) 53 | } 54 | } 55 | class SpeTheme4 extends WhiteThemeBase{ 56 | private chatFilInfo="chat_outFileInfoText" 57 | private chatFilNameText="chat_outFileNameText" 58 | 59 | constructor( 60 | id: string, 61 | tP: string, 62 | pP: string = "捕获.PNG", // 默认值 63 | mainColorSelect: string, 64 | themeType:ThemeType 65 | ) {super(id,tP,pP,mainColorSelect,themeType);} 66 | isAngleInRange(x, y, z) { 67 | let start = (x - y + 360) % 360; 68 | let end = (x + y) % 360; 69 | 70 | if (start < end) { 71 | return z >= start && z <= end; 72 | } else { 73 | return z >= start || z <= end; 74 | } 75 | } 76 | translateHue(toHueColor: number, targetS: number, targetL: number, background: Buffer, alphaT: number = 1): Buffer { 77 | // 目标hue 78 | // sideBarBgActive 为主要改变颜色 标准 79 | // @ts-ignore 80 | this.init() 81 | let {h:mainH,s:mainS}=this.mainHSL 82 | 83 | let en=this.ta.getVariablesList() 84 | for (const e of en) { 85 | let kp; 86 | let po= this.ta.get(e) 87 | // @ts-ignore 88 | let {red:r,green:g,blue:b,alpha:a} = this.ta.get(e) 89 | kp= tinycolor({ r,g,b,a}).toHsl() 90 | // hue 上下 15 91 | if(this.isAngleInRange(mainH,25,kp.h)){ 92 | kp.h=toHueColor 93 | //TODO s不能变 94 | //TODO l可以适当增减 95 | let {r:red,g:green,b:blue,a:alpha}= tinycolor(kp).toRgb() 96 | this.ta.set(e,{red,green,blue,alpha:a}) 97 | } 98 | } 99 | // console.log(alphaT) 100 | if (alphaT<1){ 101 | // log.info(`${alphaT}`) 102 | // @ts-ignore 103 | let {red:r,green:g,blue:b} = this.ta.get(this.chatBg) 104 | this.ta.set(this.chatBg,{red:r,green:g,blue:b,alpha:Math.floor(alphaT*255)}) 105 | } 106 | 107 | let {r:red,g:green,b:blue}= tinycolor({h:toHueColor,s:targetS,l:targetL}).toRgb() 108 | this.ta.set("chats_pinnedOverlay",{red,green,blue,alpha:21}) 109 | 110 | //@ts-ignore 111 | this.ta.set(this.chatFilInfo,this.ta.get(this.chatFilNameText)) 112 | this.ta.setWallpaper(background) 113 | return this.ta.toFile() 114 | } 115 | } 116 | // 创建不同的主题实例 117 | const whiteThemes = [ 118 | new SpeTheme2("white1", "yusif.attheme", "捕获.PNG", "actionBarDefaultTitle"), 119 | new SpeTheme2("white2", "Orange Flower @AloneSnowflake.attheme", "捕获.PNG", "actionBarTabLine"), 120 | new SpeTheme3("white3", "Day.attheme", "捕获.PNG", "actionBarTabLine"), 121 | new SpeTheme3("white4", "@Gumiho_tem1.attheme", "捕获.PNG", "actionBarDefault"), 122 | new SpeTheme4("white5", "Chestnut Chiffon.attheme", "捕获.PNG", "actionBarTabLine",ThemeType.SimpleDifference), 123 | // new WhiteThemeBase("white4", "Ghost.attheme", "捕获.PNG", "actionBarTabLine") 124 | ]; 125 | 126 | // 初始化 Map 并设置数据 127 | export const androidWhiteMap = new Map( 128 | whiteThemes.map(theme => [theme.id, theme]) 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /src/lib/variables/tgx-theme.ts: -------------------------------------------------------------------------------- 1 | import { adjustBrightness, isLight, themeData } from './helpers'; 2 | 3 | const tgxVariables = ( 4 | name: string, 5 | colors: string[], 6 | author: string, 7 | ): string => { 8 | const { 9 | background, 10 | filling, 11 | themeIsLight, 12 | primary, 13 | secondaryText, 14 | text, 15 | textOnPrimary, 16 | backgroundText, 17 | bubbleOutColor, 18 | } = themeData(colors); 19 | const headerBackground = themeIsLight 20 | ? primary 21 | : adjustBrightness(filling, -2); 22 | const headerText = themeIsLight ? textOnPrimary : primary; 23 | const headerTabColor = themeIsLight ? textOnPrimary : primary; 24 | const buttonIconColor = themeIsLight ? secondaryText : textOnPrimary; 25 | 26 | return ` 27 | ! 28 | name: "${name}" 29 | author: "${author}" 30 | @ 31 | shadowDepth: ${themeIsLight ? 0.65 : 1} 32 | wallpaperId: 0 33 | lightStatusBar: ${isLight(headerBackground) ? 1 : 0} 34 | dark: ${themeIsLight ? 0 : 1} 35 | parentTheme: ${themeIsLight ? 11 : 10} 36 | wallpaperUsageId: 2 37 | # 38 | background: ${background} 39 | chatBackground: ${themeIsLight ? filling : background} 40 | background_text, background_textLight, background_icon: ${backgroundText} 41 | headerBackground: ${headerBackground} 42 | headerLightBackground: ${themeIsLight ? filling : headerBackground} 43 | headerText, headerIcon, chatListAction: ${headerText} 44 | headerLightIcon, headerLightText: ${themeIsLight ? text : headerText} 45 | headerTabActiveText, headerTabActive: ${headerTabColor} 46 | headerButton: ${themeIsLight ? filling : primary} 47 | headerButtonIcon, circleButtonRegularIcon, circleButtonThemeIcon: ${buttonIconColor} 48 | iconActive, progress, controlActive, checkActive, sliderActive, togglerActive, inputActive, inlineIcon, inlineOutline, bubbleOut_inlineOutline, inlineText, bubbleOut_inlineText, bubbleOut_inlineIcon, ticks, ticksRead, bubbleOut_ticks, bubbleOut_ticksRead, bubbleOut_file, file, bubbleOut_waveformActive, waveformActive, bubbleIn_textLink, bubbleOut_textLink, textLink, chatSendButton, textSearchQueryHighlight, profileSectionActive, profileSectionActiveContent, badge, bubbleOut_chatVerticalLine, messageVerticalLine, bubbleOut_messageAuthor, messageAuthor, messageSwipeBackground, unreadText, bubble_unreadText, bubble_unreadText_noWallpaper, textNeutral, seekDone, promo, online, playerButtonActive, chatListVerify, fillingPositive, notification, notificationSecure, notificationPlayer, headerBarCallActive, fileAttach: ${primary} 49 | messageSwipeContent, fillingPositiveContent: ${textOnPrimary} 50 | 51 | passcode: ${headerBackground} 52 | passcodeIcon, passcodeText: ${headerText} 53 | circleButtonRegular, circleButtonTheme, circleButtonNewGroup, circleButtonNewSecret, circleButtonNewChannel, circleButtonNewChat: ${primary} 54 | circleButtonNewGroupIcon, circleButtonNewSecretIcon, circleButtonNewChannelIcon, circleButtonNewChatIcon: ${textOnPrimary} 55 | fileGreen: ${adjustBrightness(primary, 3)} 56 | fileYellow: ${adjustBrightness(primary, 5.5)} 57 | fileRed: ${adjustBrightness(primary, 6.3)} 58 | circleButtonChat, circleButtonOverlay: ${filling} 59 | circleButtonChatIcon, circleButtonOverlayIcon, bubbleIn_time, bubbleOut_time, bubbleOut_progress, textPlaceholder, controlInactive: ${secondaryText} 60 | headerRemoveBackgroundHighlight, introSectionActive, playerButton, text: ${text} 61 | attachContact, attachFile, attachPhoto, attachLocation, attachInlineBot: ${headerBackground} 62 | attachText: ${headerText} 63 | avatarCyan, nameCyan: ${adjustBrightness(primary, 8, themeIsLight)} 64 | avatarBlue, nameBlue: ${adjustBrightness(primary, 15, themeIsLight)} 65 | avatarGreen, nameGreen: ${adjustBrightness(primary, 4, themeIsLight)} 66 | avatarViolet, nameViolet: ${adjustBrightness(primary, 5, themeIsLight)} 67 | avatarRed, nameRed: ${adjustBrightness(primary, -2, themeIsLight)} 68 | avatarPink, namePink: ${adjustBrightness(primary, -5, themeIsLight)} 69 | avatarYellow, nameYellow: ${adjustBrightness(primary, -9, themeIsLight)} 70 | avatarOrange, nameOrange: ${adjustBrightness(primary, -12, themeIsLight)} 71 | avatarSavedMessages: ${primary} 72 | bubbleIn_background, chatKeyboard, checkContent, controlContent, filling, inlineContentActive, overlayFilling, placeholder, promoContent: ${filling} 73 | chatKeyboardButton, inputInactive, introSection, sliderInactive, textLight, chatListMute, icon, iconLight, playerCoverIcon: ${secondaryText} 74 | bubbleIn_textLinkPressHighlight, textSelectionHighlight, bubbleOut_textLinkPressHighlight, textLinkPressHighlight: ${primary}31 75 | bubbleOut_background: ${bubbleOutColor} 76 | fillingPressed: ${secondaryText}31 77 | messageSelection, bubble_messageSelectionNoWallpaper: ${primary}24 78 | bubble_messageSelection: ${primary}48 79 | headerRemoveBackground: ${text}96 80 | bubble_messageCheckOutline, bubble_messageCheckOutlineNoWallpaper: ${text}42 81 | bubbleOut_waveformInactive, waveformInactive: ${secondaryText}74 82 | bubbleOut_text, bubbleIn_text: ${text} 83 | seekEmpty, seekReady, playerCoverPlaceholder: ${text}19 84 | headerTabInactiveText: ${headerText}97 85 | togglerActiveBackground: ${primary}97 86 | unread, bubble_unread, bubble_unread_noWallpaper: ${primary}18 87 | previewBackground: ${filling}C0 88 | togglerInactive: ${adjustBrightness(secondaryText, 10, themeIsLight)} 89 | togglerInactiveBackground: ${adjustBrightness( 90 | secondaryText, 91 | 10, 92 | themeIsLight, 93 | )}64 94 | badgeText: ${textOnPrimary} 95 | badgeMuted: ${text}65 96 | badgeMutedText: ${filling} 97 | separator: #00000023`; 98 | }; 99 | 100 | export default tgxVariables; 101 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const parseContents_1 = require("./parseContents"); 4 | const serializeTheme_1 = require("./serializeTheme"); 5 | class Attheme { 6 | /** 7 | * Constructs a new theme. 8 | * @param contents The .attheme contents to parse. 9 | */ 10 | constructor(contents) { 11 | this._variables = new Map(); 12 | this._wallpaper = null; 13 | if (typeof contents === `string`) { 14 | const { variables, wallpaper } = parseContents_1.default(contents); 15 | this._variables = variables; 16 | this._wallpaper = wallpaper; 17 | } 18 | else if (contents) { 19 | const iterator = contents[Symbol.iterator](); 20 | while (true) { 21 | const result = iterator.next(); 22 | if (result.done) { 23 | break; 24 | } 25 | else { 26 | const [variable, color] = result.value; 27 | this._variables.set(variable, Object.assign({}, color)); 28 | } 29 | } 30 | } 31 | } 32 | /** 33 | * Gets the theme's wallpaper. 34 | * @returns The theme's wallpaper. 35 | */ 36 | getWallpaper() { 37 | return this._wallpaper ? this._wallpaper : null; 38 | } 39 | /** 40 | * Sets the new wallpaper. 41 | * @param newWallpaper The new wallpaper. 42 | */ 43 | setWallpaper(newWallpaper) { 44 | this._wallpaper = newWallpaper; 45 | } 46 | /** 47 | * Checks whether the theme has a wallpaper. 48 | * @returns Whether the theme has a wallpaper. 49 | */ 50 | hasWallpaper() { 51 | return typeof this._wallpaper === `string`; 52 | } 53 | /** 54 | * Deletes the wallpaper. 55 | */ 56 | deleteWallpaper() { 57 | delete this._wallpaper; 58 | } 59 | /** 60 | * Checks whether the theme has the specified variables. 61 | * @param variable The varialbe to check existence of. 62 | * @returns Whehter the theme has the variable. 63 | */ 64 | has(variable) { 65 | return this._variables.has(variable); 66 | } 67 | /** 68 | * Deletes the variable. 69 | * @param variable The variable to delete. 70 | */ 71 | delete(variable) { 72 | this._variables.delete(variable); 73 | } 74 | /** 75 | * Gets the value of the specified variable. 76 | * @param variable The variable to get value of. 77 | * @returns The value of the variable. 78 | */ 79 | get(variable) { 80 | const value = this._variables.get(variable); 81 | return value === undefined ? null : Object.assign({}, value); 82 | } 83 | /** 84 | * Sets the variable to the specified value. 85 | * @param variable The variable to set. 86 | * @param value The value of the variable. 87 | */ 88 | set(variable, value) { 89 | this._variables.set(variable, Object.assign({}, value)); 90 | } 91 | /** 92 | * Gets the amount of variables in the theme. 93 | * @returns The amount of variable in the theme. 94 | */ 95 | getVariablesAmount() { 96 | return this._variables.size; 97 | } 98 | /** 99 | * Gets an array of all variables in the theme. 100 | * @returns An array of varialbes. 101 | */ 102 | getVariablesList() { 103 | return [...this._variables.keys()]; 104 | } 105 | /** 106 | * Sorts the theme's variables by their names in place. 107 | */ 108 | sort() { 109 | this._variables = new Map([...this._variables].sort(([a], [b]) => a.localeCompare(b))); 110 | } 111 | /** 112 | * Fallbacks this theme to another theme: 113 | * 114 | * - Every variable which is present in `other` but not in `this`, is copied 115 | * to `this`. 116 | * - If `this` doesn't have a wallpaper, the wallpaper is copied from `other`. 117 | * 118 | * @param other The other theme to fallback to. 119 | */ 120 | fallbackToOther(other) { 121 | for (const [variable, color] of other._variables) { 122 | if (!this._variables.has(variable)) { 123 | this._variables.set(variable, Object.assign({}, color)); 124 | } 125 | } 126 | if (typeof this._wallpaper !== `string`) { 127 | this._wallpaper = other._wallpaper; 128 | } 129 | } 130 | /** 131 | * Fallbacks variables to other existing variabled according to the map. 132 | * @param fallbackMap Defines variable mapping. 133 | */ 134 | fallbackToSelf(fallbackMap) { 135 | for (const [variable, fallback] of fallbackMap) { 136 | if (!this._variables.has(variable) && this._variables.has(fallback)) { 137 | this._variables.set(variable, Object.assign({}, this._variables.get(fallback))); 138 | } 139 | } 140 | } 141 | *[Symbol.iterator]() { 142 | for (const [variable, value] of this._variables) { 143 | const entry = [variable, Object.assign({}, value)]; 144 | yield entry; 145 | } 146 | } 147 | /** 148 | * Serializes the theme. 149 | * @param colorSignature The way the colors should be serialized. "hex" for 150 | * #aarrggbb and "int" for Java int color. 151 | * @returns The serialized theme. 152 | */ 153 | toString(colorSignature) { 154 | return serializeTheme_1.default(this, colorSignature); 155 | } 156 | } 157 | exports.default = Attheme; 158 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /src/lib/preview/fallbacks.js: -------------------------------------------------------------------------------- 1 | const fallbacks = [ 2 | [`chat_adminText`, `chat_inTimeText`], 3 | [`chat_adminSelectedText`, `chat_inTimeSelectedText`], 4 | [`key_player_progressCachedBackground`, `player_progressBackground`], 5 | [`chat_inAudioCacheSeekbar`, `chat_inAudioSeekbar`], 6 | [`chat_outAudioCacheSeekbar`, `chat_outAudioSeekbar`], 7 | [`chat_emojiSearchBackground`, `chat_emojiPanelStickerPackSelector`], 8 | [`location_sendLiveLocationIcon`, `location_sendLocationIcon`], 9 | [`changephoneinfo_image2`, `featuredStickers_addButton`], 10 | [`key_graySectionText`, `windowBackgroundWhiteGrayText2`], 11 | [`chat_inMediaIcon`, `chat_inBubble`], 12 | [`chat_outMediaIcon`, `chat_outBubble`], 13 | [`chat_inMediaIconSelected`, `chat_inBubbleSelected`], 14 | [`chat_outMediaIconSelected`, `chat_outBubbleSelected`], 15 | [`chats_actionUnreadIcon`, `profile_actionIcon`], 16 | [`chats_actionUnreadBackground`, `profile_actionBackground`], 17 | [`chats_actionUnreadPressedBackground`, `profile_actionPressedBackground`], 18 | [`dialog_inlineProgressBackground`, `windowBackgroundGray`], 19 | [`dialog_inlineProgress`, `chats_menuItemIcon`], 20 | [`groupcreate_spanDelete`, `chats_actionIcon`], 21 | [`sharedMedia_photoPlaceholder`, `windowBackgroundGray`], 22 | [`chat_attachPollBackground`, `chat_attachAudioBackground`], 23 | [`chat_attachPollIcon`, `chat_attachAudioIcon`], 24 | [`chats_onlineCircle`, `windowBackgroundWhiteBlueText`], 25 | [`windowBackgroundWhiteBlueButton`, `windowBackgroundWhiteValueText`], 26 | [`windowBackgroundWhiteBlueIcon`, `windowBackgroundWhiteValueText`], 27 | [`undo_background`, `chat_gifSaveHintBackground`], 28 | [`undo_cancelColor`, `chat_gifSaveHintText`], 29 | [`undo_infoColor`, `chat_gifSaveHintText`], 30 | [`windowBackgroundUnchecked`, `windowBackgroundWhite`], 31 | [`windowBackgroundChecked`, `windowBackgroundWhite`], 32 | [`switchTrackBlue`, `switchTrack`], 33 | [`switchTrackBlueChecked`, `switchTrackChecked`], 34 | [`switchTrackBlueThumb`, `windowBackgroundWhite`], 35 | [`switchTrackBlueThumbChecked`, `windowBackgroundWhite`], 36 | [`windowBackgroundCheckText`, `windowBackgroundWhiteBlackText`], 37 | [`contextProgressInner4`, `contextProgressInner1`], 38 | [`contextProgressOuter4`, `contextProgressOuter1`], 39 | [`switchTrackBlueSelector`, `listSelector`], 40 | [`switchTrackBlueSelectorChecked`, `listSelector`], 41 | [`chat_emojiBottomPanelIcon`, `chat_emojiPanelIcon`], 42 | [`chat_emojiSearchIcon`, `chat_emojiPanelIcon`], 43 | [`chat_emojiPanelStickerSetNameHighlight`, `windowBackgroundWhiteBlueText4`], 44 | [`chat_emojiPanelStickerPackSelectorLine`, `chat_emojiPanelIconSelected`], 45 | [`sharedMedia_actionMode`, `actionBarDefault`], 46 | [`key_sheet_scrollUp`, `chat_emojiPanelStickerPackSelector`], 47 | [`sheet_other`, `player_actionBarItems`], 48 | [`dialogSearchBackground`, `chat_emojiPanelStickerPackSelector`], 49 | [`dialogSearchHint`, `chat_emojiPanelIcon`], 50 | [`dialogSearchIcon`, `chat_emojiPanelIcon`], 51 | [`dialogSearchText`, `windowBackgroundWhiteBlackText`], 52 | [`dialogFloatingButton`, `dialogRoundCheckBox`], 53 | [`dialogFloatingButtonPressed`, `dialogRoundCheckBox`], 54 | [`dialogFloatingIcon`, `dialogRoundCheckBoxCheck`], 55 | [`dialogShadowLine`, `chat_emojiPanelShadowLine`], 56 | [`actionBarDefaultArchived`, `actionBarDefault`], 57 | [`actionBarDefaultArchivedSelector`, `actionBarDefaultSelector`], 58 | [`actionBarDefaultArchivedIcon`, `actionBarDefaultIcon`], 59 | [`actionBarDefaultArchivedTitle`, `actionBarDefaultTitle`], 60 | [`actionBarDefaultArchivedSearch`, `actionBarDefaultSearch`], 61 | [ 62 | `actionBarDefaultArchivedSearchPlaceholder`, 63 | `actionBarDefaultSearchPlaceholder`, 64 | ], 65 | [`chats_message_threeLines`, `chats_message`], 66 | [`chats_nameMessage_threeLines`, `chats_nameMessage`], 67 | [`chats_nameArchived`, `chats_name`], 68 | [`chats_nameMessageArchived`, `chats_nameMessage`], 69 | [`chats_nameMessageArchived_threeLines`, `chats_nameMessage`], 70 | [`chats_messageArchived`, `chats_message`], 71 | [`avatar_backgroundArchived`, `chats_unreadCounterMuted`], 72 | [`avatar_backgroundArchivedHidden`, `chats_unreadCounterMuted`], 73 | [`chats_archiveBackground`, `chats_actionBackground`], 74 | [`chats_archivePinBackground`, `chats_unreadCounterMuted`], 75 | [`chats_archiveIcon`, `chats_actionIcon`], 76 | [`chats_archiveText`, `chats_actionIcon`], 77 | [`actionBarDefaultSubmenuItemIcon`, `dialogIcon`], 78 | [`checkboxDisabled`, `chats_unreadCounterMuted`], 79 | [`chat_status`, `actionBarDefaultSubtitle`], 80 | [`chat_inGreenCall`, `calls_callReceivedGreenIcon`], 81 | [`chat_inRedCall`, `calls_callReceivedRedIcon`], 82 | [`chat_outGreenCall`, `calls_callReceivedGreenIcon`], 83 | [`actionBarTabActiveText`, `actionBarDefaultTitle`], 84 | [`actionBarTabUnactiveText`, `actionBarDefaultSubtitle`], 85 | [`actionBarTabLine`, `actionBarDefaultTitle`], 86 | [`actionBarTabSelector`, `actionBarDefaultSelector`], 87 | [`profile_status`, `avatar_subtitleInProfileBlue`], 88 | [`chats_menuTopBackgroundCats`, `avatar_backgroundActionBarBlue`], 89 | [`chat_messagePanelSendPressed`, `key_chat_messagePanelVoicePressed`], 90 | [`chat_attachPermissionImage`, `dialogTextBlack`], 91 | [`chat_attachPermissionMark`, `chat_sentError`], 92 | [`chat_attachPermissionText`, `dialogTextBlack`], 93 | [`chat_attachEmptyImage`, `emptyListPlaceholder`], 94 | [`actionBarBrowser`, `actionBarDefault`], 95 | [`chats_sentReadCheck`, `chats_sentCheck`], 96 | [`chat_outSentCheckRead`, `chat_outSentCheck`], 97 | [`chat_outSentCheckReadSelected`, `chat_outSentCheckSelected`], 98 | ]; 99 | module.exports = { fallbacks }; 100 | -------------------------------------------------------------------------------- /src/mianwrapper/android/OriginImpletement/theme-colors-and-pic-api.ts: -------------------------------------------------------------------------------- 1 | import sharp from 'sharp'; 2 | import http from "node:http"; 3 | import url from "url"; 4 | import fun from "imageinfo" 5 | import Vibrant from 'node-vibrant' 6 | import loge from "../../../lib/config/log_config"; 7 | import setColor from "../../../lib/picserver/svgps"; 8 | import {createTheme} from "../../../lib/util/themes"; 9 | 10 | let log=loge.getLogger(`${__filename}`); 11 | //产生颜色数组 和颜色预览图片 12 | //第一次 请求 13 | export async function basePicCreateColorPic(req:http.IncomingMessage, res:http.ServerResponse){ 14 | // @ts-ignore 15 | const body: any[] | readonly Uint8Array[]= []; 16 | let pic:Buffer; 17 | log.info(`创建颜色预览 url 为 ${req.url}`) 18 | try { 19 | // @ts-ignore 20 | const urlObject = url.parse(req.url); 21 | const {pathname, query} = urlObject; 22 | const method = req.method; 23 | if (method == 'POST' && pathname === '/colorlist') { 24 | req.on('data', chunk => { 25 | // @ts-ignore 26 | body.push(chunk); 27 | }); 28 | 29 | req.on('end', async () => { 30 | let arrs 31 | pic = Buffer.concat(body); 32 | let info= fun(pic) 33 | try { 34 | // 获取图片色彩颜色数组 35 | let palette= await Vibrant.from(pic).quality(10).getPalette() 36 | //@ts-ignore 37 | arrs=[palette.Vibrant.hex, 38 | //@ts-ignore 39 | palette.LightVibrant.hex, 40 | //@ts-ignore 41 | palette.DarkVibrant.hex, 42 | //@ts-ignore 43 | palette.DarkMuted.hex, 44 | //@ts-ignore 45 | palette.Muted.hex 46 | ]; 47 | 48 | //arrs= await getImageColors(pic, info.mimeType) 49 | log.info(arrs) 50 | }catch (e){ 51 | log.error(`getImageColors生成颜色数组异常 : ${e}`) 52 | res.end("fail") 53 | return; 54 | } 55 | 56 | let str=setColor(arrs) //获取svg字符串 57 | if (str===null){ 58 | log.error("svg 字符串为空! ") 59 | res.end("fail") 60 | return ; 61 | } 62 | // @ts-ignore 63 | const data = await sharp(Buffer.from(str)) 64 | .toBuffer(); 65 | let objs = { 66 | arr: arrs, photo: data.toString('base64') 67 | }; 68 | res.end(JSON.stringify(objs)); 69 | }); 70 | } else { 71 | return; 72 | } 73 | }catch (e){ 74 | log.error(`未知异常,调用栈:${e}`) 75 | res.end("fail") 76 | } 77 | 78 | } 79 | //第二次请求 80 | //请求体为 三个颜色参数 一张图片 返回 attheme 81 | export async function basePicCreateTheme(req:http.IncomingMessage, res:http.ServerResponse){ 82 | // @ts-ignore 83 | log.info(`正在创建attheme url 为 ${req.url}`) 84 | //@ts-ignore 85 | let body=req.body; //base64 格式 86 | try { 87 | // @ts-ignore 88 | const urlObject = url.parse(req.url); 89 | const { pathname,query } = urlObject; 90 | const method = req.method; 91 | 92 | if (method==='POST'&&(pathname==='/attheme-create'||pathname==='/attheme-create/tran')){ 93 | const picObj=body; 94 | let buffer = Buffer.from(picObj?.picb,'base64'); 95 | const type = pathname === '/attheme-create' ? 'attheme' : 'attheme-tran'; 96 | let coarr=Array(3) 97 | coarr[0]=picObj?.colors[0] 98 | coarr[1]=picObj?.colors[1] 99 | coarr[2]=picObj?.colors[2] 100 | // let text_color= getTextColor(picObj?.colors[1]) 101 | // if (text_color!==null){ 102 | // coarr[1]=text_color 103 | // console.log(text_color) 104 | // } 105 | // console.log(text_color) 106 | // console.log(coarr) 107 | let theme= createTheme({ 108 | username:'', 109 | image:buffer, 110 | name:'', 111 | colors:coarr, 112 | type:type, 113 | }); 114 | res.end(Buffer.from(theme, 'binary')) //返回的时二进制 最后默认响应的是二进制 115 | 116 | }else { 117 | return; 118 | } 119 | }catch (e){ 120 | res.end("fail") 121 | } 122 | } 123 | 124 | 125 | /** 126 | //获取buffer 127 | const fp=fs.readFileSync('../pic/FugA0ydXwAAAdRH.jpg'); 128 | // eslint-disable-next-line @typescript-eslint/no-empty-function 129 | (async ()=> { 130 | //获取图片颜色数组 131 | const arr= await getImageColors(fp,'image/jpeg'); 132 | let str=setColor(arr); //返回 色彩列表图片 buffer 133 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 134 | // @ts-ignore 135 | const data = await sharp(Buffer.from(str)) 136 | .toBuffer(); 137 | // fs.writeFileSync('output.png',data); 138 | 139 | 140 | //根据 列表颜色创建主题 141 | let username='mki'; 142 | let name='opo'; 143 | let theme= createTheme({ 144 | username:username, 145 | image:fp, 146 | name:name, 147 | colors:arr.slice(0,3), 148 | type:'attheme', 149 | }); 150 | 151 | fs.writeFileSync('output.attheme',Buffer.from(theme, 'binary')); 152 | 153 | })(); 154 | */ 155 | 156 | /** 157 | 1.首先 python端接受 /create attheme base on pic 命令 158 | 2.python 向用户 去请求 一张图片 并且验证图片大小 159 | 3.不符合大小 请求修改 重传 160 | 4.进入准备阶段 依据此图片 生成 色彩图片 // 封装为一个服务方法 161 | 5.python端依据此图片 发送调查报告 供用户填写 162 | 6.将此报批稿发送给 node 端 生成主题 // 封装为一个服务方法 163 | 7.python端将此主题发送给用户 并考虑做后续处理 如保存主题至 主机 164 | */ 165 | -------------------------------------------------------------------------------- /src/lib/atthemejs/lib/fallbacks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * A fallback map used by Telegram. 5 | */ 6 | const FALLBACKS = new Map([ 7 | [`chat_adminText`, `chat_inTimeText`], 8 | [`chat_adminSelectedText`, `chat_inTimeSelectedText`], 9 | [`key_player_progressCachedBackground`, `player_progressBackground`], 10 | [`chat_inAudioCacheSeekbar`, `chat_inAudioSeekbar`], 11 | [`chat_outAudioCacheSeekbar`, `chat_outAudioSeekbar`], 12 | [`chat_emojiSearchBackground`, `chat_emojiPanelStickerPackSelector`], 13 | [`location_sendLiveLocationIcon`, `location_sendLocationIcon`], 14 | [`changephoneinfo_image2`, `featuredStickers_addButton`], 15 | [`key_graySectionText`, `windowBackgroundWhiteGrayText2`], 16 | [`chat_inMediaIcon`, `chat_inBubble`], 17 | [`chat_outMediaIcon`, `chat_outBubble`], 18 | [`chat_inMediaIconSelected`, `chat_inBubbleSelected`], 19 | [`chat_outMediaIconSelected`, `chat_outBubbleSelected`], 20 | [`chats_actionUnreadIcon`, `profile_actionIcon`], 21 | [`chats_actionUnreadBackground`, `profile_actionBackground`], 22 | [`chats_actionUnreadPressedBackground`, `profile_actionPressedBackground`], 23 | [`dialog_inlineProgressBackground`, `windowBackgroundGray`], 24 | [`dialog_inlineProgress`, `chats_menuItemIcon`], 25 | [`groupcreate_spanDelete`, `chats_actionIcon`], 26 | [`sharedMedia_photoPlaceholder`, `windowBackgroundGray`], 27 | [`chat_attachPollBackground`, `chat_attachAudioBackground`], 28 | [`chat_attachPollIcon`, `chat_attachAudioIcon`], 29 | [`chats_onlineCircle`, `windowBackgroundWhiteBlueText`], 30 | [`windowBackgroundWhiteBlueButton`, `windowBackgroundWhiteValueText`], 31 | [`windowBackgroundWhiteBlueIcon`, `windowBackgroundWhiteValueText`], 32 | [`undo_background`, `chat_gifSaveHintBackground`], 33 | [`undo_cancelColor`, `chat_gifSaveHintText`], 34 | [`undo_infoColor`, `chat_gifSaveHintText`], 35 | [`windowBackgroundUnchecked`, `windowBackgroundWhite`], 36 | [`windowBackgroundChecked`, `windowBackgroundWhite`], 37 | [`switchTrackBlue`, `switchTrack`], 38 | [`switchTrackBlueChecked`, `switchTrackChecked`], 39 | [`switchTrackBlueThumb`, `windowBackgroundWhite`], 40 | [`switchTrackBlueThumbChecked`, `windowBackgroundWhite`], 41 | [`windowBackgroundCheckText`, `windowBackgroundWhiteBlackText`], 42 | [`contextProgressInner4`, `contextProgressInner1`], 43 | [`contextProgressOuter4`, `contextProgressOuter1`], 44 | [`switchTrackBlueSelector`, `listSelector`], 45 | [`switchTrackBlueSelectorChecked`, `listSelector`], 46 | [`chat_emojiBottomPanelIcon`, `chat_emojiPanelIcon`], 47 | [`chat_emojiSearchIcon`, `chat_emojiPanelIcon`], 48 | [`chat_emojiPanelStickerSetNameHighlight`, `windowBackgroundWhiteBlueText4`], 49 | [`chat_emojiPanelStickerPackSelectorLine`, `chat_emojiPanelIconSelected`], 50 | [`sharedMedia_actionMode`, `actionBarDefault`], 51 | [`key_sheet_scrollUp`, `chat_emojiPanelStickerPackSelector`], 52 | [`sheet_other`, `player_actionBarItems`], 53 | [`dialogSearchBackground`, `chat_emojiPanelStickerPackSelector`], 54 | [`dialogSearchHint`, `chat_emojiPanelIcon`], 55 | [`dialogSearchIcon`, `chat_emojiPanelIcon`], 56 | [`dialogSearchText`, `windowBackgroundWhiteBlackText`], 57 | [`dialogFloatingButton`, `dialogRoundCheckBox`], 58 | [`dialogFloatingButtonPressed`, `dialogRoundCheckBox`], 59 | [`dialogFloatingIcon`, `dialogRoundCheckBoxCheck`], 60 | [`dialogShadowLine`, `chat_emojiPanelShadowLine`], 61 | [`actionBarDefaultArchived`, `actionBarDefault`], 62 | [`actionBarDefaultArchivedSelector`, `actionBarDefaultSelector`], 63 | [`actionBarDefaultArchivedIcon`, `actionBarDefaultIcon`], 64 | [`actionBarDefaultArchivedTitle`, `actionBarDefaultTitle`], 65 | [`actionBarDefaultArchivedSearch`, `actionBarDefaultSearch`], 66 | [ 67 | `actionBarDefaultArchivedSearchPlaceholder`, 68 | `actionBarDefaultSearchPlaceholder`, 69 | ], 70 | [`chats_message_threeLines`, `chats_message`], 71 | [`chats_nameMessage_threeLines`, `chats_nameMessage`], 72 | [`chats_nameArchived`, `chats_name`], 73 | [`chats_nameMessageArchived`, `chats_nameMessage`], 74 | [`chats_nameMessageArchived_threeLines`, `chats_nameMessage`], 75 | [`chats_messageArchived`, `chats_message`], 76 | [`avatar_backgroundArchived`, `chats_unreadCounterMuted`], 77 | [`avatar_backgroundArchivedHidden`, `chats_unreadCounterMuted`], 78 | [`chats_archiveBackground`, `chats_actionBackground`], 79 | [`chats_archivePinBackground`, `chats_unreadCounterMuted`], 80 | [`chats_archiveIcon`, `chats_actionIcon`], 81 | [`chats_archiveText`, `chats_actionIcon`], 82 | [`actionBarDefaultSubmenuItemIcon`, `dialogIcon`], 83 | [`checkboxDisabled`, `chats_unreadCounterMuted`], 84 | [`chat_status`, `actionBarDefaultSubtitle`], 85 | [`chat_inGreenCall`, `calls_callReceivedGreenIcon`], 86 | [`chat_inRedCall`, `calls_callReceivedRedIcon`], 87 | [`chat_outGreenCall`, `calls_callReceivedGreenIcon`], 88 | [`actionBarTabActiveText`, `actionBarDefaultTitle`], 89 | [`actionBarTabUnactiveText`, `actionBarDefaultSubtitle`], 90 | [`actionBarTabLine`, `actionBarDefaultTitle`], 91 | [`actionBarTabSelector`, `actionBarDefaultSelector`], 92 | [`profile_status`, `avatar_subtitleInProfileBlue`], 93 | [`chats_menuTopBackgroundCats`, `avatar_backgroundActionBarBlue`], 94 | [`chat_messagePanelSendPressed`, `key_chat_messagePanelVoicePressed`], 95 | [`chat_attachPermissionImage`, `dialogTextBlack`], 96 | [`chat_attachPermissionMark`, `chat_sentError`], 97 | [`chat_attachPermissionText`, `dialogTextBlack`], 98 | [`chat_attachEmptyImage`, `emptyListPlaceholder`], 99 | [`actionBarBrowser`, `actionBarDefault`], 100 | [`chats_sentReadCheck`, `chats_sentCheck`], 101 | [`chat_outSentCheckRead`, `chat_outSentCheck`], 102 | [`chat_outSentCheckReadSelected`, `chat_outSentCheckSelected`], 103 | ]); 104 | exports.default = FALLBACKS; 105 | //# sourceMappingURL=fallbacks.js.map -------------------------------------------------------------------------------- /src/bot/bot.ts: -------------------------------------------------------------------------------- 1 | import process from "node:process"; 2 | import { devObject, proObject } from "../../config"; 3 | import { HttpsProxyAgent } from "https-proxy-agent"; 4 | import path from "path"; 5 | import logger from "../lib/config/log_config"; 6 | import iMod from "../db/mysql-use"; 7 | import map from "../restore/pubmap"; 8 | import * as fs from "node:fs"; 9 | import { Bot, InputFile } from "grammy"; 10 | import render from "../lib/preview/render-pool"; 11 | import { 12 | MINIMALISTIC_TEMPLATE, // 移动端主题 13 | REGULAR_TEMPLATE, // 移动端主题 14 | NEW_TEMPLATE, 15 | DESKTOP_TEMPLATE, // 桌面模板 16 | } from "../lib/preview/preview-maker.js"; 17 | import axios from "axios"; 18 | 19 | let log = logger.getLogger(`${__filename}`); 20 | let botApi: string; 21 | let httpAgent: any; 22 | 23 | if (process.env.NODE_ENV !== "dev") { 24 | botApi = proObject.botApi; 25 | } else { 26 | botApi = devObject.botApi; 27 | httpAgent = new HttpsProxyAgent("http://127.0.0.1:10810"); 28 | } 29 | 30 | let bot = new Bot(botApi); 31 | 32 | bot.command("start", async (ctx) => { 33 | try { 34 | let basePath = "public/myserver-bot-public/source"; 35 | let arStr = ctx.match; 36 | if (arStr.length == 0) return; 37 | let fileName = arStr.substring(0, arStr.length - 1); 38 | if (arStr.endsWith("A")) { 39 | iMod.jump_to_theme.create({ 40 | type: 0, 41 | theme_name: fileName, 42 | date: new Date(), 43 | }); 44 | fileName = fileName + ".attheme"; 45 | let filePath = path.join(basePath, "attheme", fileName); 46 | await ctx.replyWithDocument(new InputFile( fs.readFileSync(filePath), path.join("attheme", fileName))); 47 | } 48 | 49 | if (arStr.endsWith("D")) { 50 | iMod.jump_to_theme.create({ 51 | type: 1, 52 | theme_name: fileName, 53 | date: new Date(), 54 | }); 55 | fileName = fileName + ".tdesktop-theme"; 56 | let filePath = path.join(basePath, "desk", fileName); 57 | await ctx.replyWithDocument(new InputFile( fs.readFileSync(filePath), 58 | path.join("desk", fileName))); 59 | } 60 | //安卓主题文件 61 | if (arStr.endsWith("L")) { 62 | let fd = map.get(ctx.match); 63 | if(fd===undefined) return; 64 | let buffer = fs.readFileSync(fd.tempName); 65 | await ctx.reply("请使用安卓版telegram打开主题文件"); 66 | await ctx.replyWithDocument(new InputFile(buffer,fd.themeName + ".attheme")); 67 | fs.rmSync(fd.tempName); 68 | log.info(`已经使用Bot跳转,路径 ${fd.tempName}`); 69 | } 70 | if (arStr.endsWith("M")) { 71 | let fd = map.get(ctx.match); 72 | if(fd===undefined) return; 73 | let buffer = fs.readFileSync(fd.tempName); 74 | await ctx.reply("请使用桌面版telegram打开主题文件"); 75 | await ctx.replyWithDocument(new InputFile( 76 | buffer,fd.themeName + ".tdesktop-theme")); 77 | fs.rmSync(fd.tempName); 78 | log.info(`已经使用Bot跳转,路径 ${fd.tempName}`); 79 | } 80 | } catch (e) { 81 | log.error(e); 82 | } 83 | }); 84 | //TODO 机器人关于主题的制作完全是 基于已有主题匹配 85 | 86 | // bot.command("getPreview", async (ctx) => { 87 | // ctx.session.waitingForTheme = true; 88 | // await ctx.reply("请发送主题文件"); 89 | // }); 90 | // bot.on(message("document"), async (ctx, next) => { 91 | // log.info(`收到文档: ${ctx.message.document.file_name} from user ${ctx.from.id}`); 92 | // if (!ctx.session?.waitingForTheme) return next(); 93 | // ctx.session.waitingForTheme = false; 94 | // const doc = ctx.message.document; 95 | // await ctx.reply("主题文件已收到,正在生成预览…"); 96 | // 97 | // try { 98 | // // 获取文件信息和下载链接 99 | // const fileInfo = await ctx.telegram.getFile(doc.file_id); 100 | // const fileLink = await ctx.telegram.getFileLink(doc.file_id); 101 | // log.info(`文件链接: ${fileLink}, 文件路径: ${fileInfo}`); 102 | // // 验证文件大小(可选,限制 20MB) 103 | // if (doc.file_size > 20 * 1024 * 1024) { 104 | // await ctx.reply("文件过大,请上传小于 20MB 的文件"); 105 | // return; 106 | // } 107 | // 108 | // // 下载文件为 Buffer 109 | // const response = await axios.get(fileLink.href, { 110 | // httpsAgent:new HttpsProxyAgent("socks5://127.0.0.1:10810"), 111 | // proxy: false, // 禁用 axios 的 proxy 自动处理 112 | // responseType:"arraybuffer", 113 | // // 添加 Telegram API 所需的 headers(可选) 114 | // // 增加超时时间 115 | // timeout: 30000, // 30 秒 116 | // }).catch(async (error) => { 117 | // if (error.response?.status === 400) { 118 | // log.error(`Axios 400 错误,文件链接: ${fileLink}, 错误详情: ${error.message}`); 119 | // throw new Error("无法下载文件:Telegram API 返回 400 Bad Request,可能文件 ID 无效或已过期"); 120 | // } 121 | // throw error; // 抛出其他错误 122 | // }); 123 | // 124 | // const themeBuffer = Buffer.from(response.data); 125 | // let template; 126 | // 127 | // // 根据文件扩展名选择模板 128 | // if (fileInfo.file_path.endsWith(".tdesktop-theme")) { 129 | // template = DESKTOP_TEMPLATE; 130 | // } else if (fileInfo.file_path.endsWith(".attheme")) { 131 | // template = MINIMALISTIC_TEMPLATE; 132 | // } else { 133 | // await ctx.reply("不支持的文件类型,仅支持 .attheme 和 .tdesktop-theme 文件"); 134 | // return; 135 | // } 136 | // 137 | // // 渲染预览 138 | // const preview = await render({ 139 | // themeBuffer, 140 | // name: doc.file_name || "theme", 141 | // template, 142 | // }); 143 | // 144 | // // 发送预览图片 145 | // await ctx.replyWithPhoto({ source: preview }); 146 | // await ctx.reply("预览生成成功!"); 147 | // 148 | // // 清理临时文件(如果 preview 是文件路径) 149 | // if (typeof preview === "string" && fs.existsSync(preview)) { 150 | // fs.rmSync(preview); 151 | // log.info(`已清理临时文件: ${preview}`); 152 | // } 153 | // } catch (error) { 154 | // log.error(`处理文档时出错 (user: ${ctx.from.id}, file: ${doc.file_name}):`, error); 155 | // await ctx.reply(`生成预览时出错:${error.message || "未知错误"},请稍后重试`); 156 | // } 157 | // }); 158 | 159 | 160 | 161 | export { bot}; 162 | -------------------------------------------------------------------------------- /src/express/http-config.ts: -------------------------------------------------------------------------------- 1 | import logger from "../lib/config/log_config"; 2 | import checkDirectoriesAn from "../mianwrapper/desktop-check"; 3 | import checkDireDe from "../mianwrapper/android-check"; 4 | import express from "express"; 5 | import cors from "cors"; 6 | import BodyParser from "body-parser"; 7 | import {exportA, exportD} from "../mianwrapper/theme-map-check"; 8 | import { 9 | basePicCreateColorPic, 10 | basePicCreateTheme 11 | } from "../mianwrapper/android/OriginImpletement/theme-colors-and-pic-api"; 12 | import {createPreview} from "../mianwrapper/preview/theme-preview-api"; 13 | import {basePicCreateDesktop} from "../mianwrapper/desktop/OriginImpletement/theme-desktop-api"; 14 | import {AndroidBlack} from "../mianwrapper/templete/android/black"; 15 | import {AndroidWhite} from "../mianwrapper/templete/android/white"; 16 | import {DesktopBlack} from "../mianwrapper/templete/desktop/black"; 17 | import {DesktopWhite} from "../mianwrapper/templete/desktop/white"; 18 | import {dataUriToBuffer} from "data-uri-to-buffer"; 19 | import process from "node:process"; 20 | import * as fs from "node:fs"; 21 | import https from "node:https"; 22 | import http from "http"; 23 | import db from "../db/mysql-use" 24 | import {theme_editor_logAttributes} from "../db/models/theme_editor_log"; 25 | import themeInfo from "../mianwrapper/templete/base-theme-info" 26 | import tmp from 'tmp' 27 | import map from "../restore/pubmap"; 28 | let log=logger.getLogger(`${__filename}`); 29 | //目录对应模板集合 30 | const targetAnb = 'public/tempelete/tohuemodle/android/black'; // 替换为你的目录路径 31 | const targetAnw = 'public/tempelete/tohuemodle/android/white'; // 替换为你的目录路径 32 | checkDirectoriesAn(targetAnb); 33 | checkDirectoriesAn(targetAnw); 34 | 35 | const targetBl = 'public/tempelete/tohuemodle/desktop/black'; // 替换为你的目录路径 36 | const targetWh='public/tempelete/tohuemodle/desktop/white' 37 | checkDireDe(targetBl); 38 | checkDireDe(targetWh); 39 | const port=3000; 40 | 41 | let app = express() 42 | const staticPath="public/tempelete/tohuemodle" 43 | app.use(cors({ 44 | origin: '*' // 允许的来源 45 | })) 46 | 47 | let statics=["public/tempelete/tohuemodle", 48 | "public/myserver-bot-public/attheme", 49 | "public/myserver-bot-public/desk", 50 | "public/myserver-bot-public/source/attheme", 51 | "public/myserver-bot-public/desk", 52 | ] 53 | statics.forEach(s=>{ 54 | app.use(express.static(s,{ 55 | cacheControl:true, 56 | maxAge: 604800 57 | })) 58 | }) 59 | 60 | 61 | app.use(BodyParser.json({limit: '210000kb'})) 62 | 63 | app.use((err, req, res, next) => { 64 | // logic 65 | log.error(err.toString()); 66 | }) 67 | 68 | 69 | app.get('/attheme', (req, res) => { 70 | res.send(exportA) 71 | }) 72 | app.get('/desk', (req, res) => { 73 | res.send(exportD) 74 | }) 75 | 76 | app.get('/test', (req, res) => { 77 | res.send('Hello World!') 78 | }) 79 | //获取图片的 颜色图片 80 | app.post("/colorlist",async (req,res)=>{ 81 | await basePicCreateColorPic(req,res); 82 | }) 83 | //安卓预览创建 84 | // app.post("/android*",async(req,res)=>{ 85 | // await createPreview(req,res); 86 | // }) 87 | // //桌面预览创建 88 | // app.post("/desktop*",async(req,res)=>{ 89 | // await createPreview(req,res); 90 | // }) 91 | 92 | //创建 不透明安卓主题 93 | app.post("/attheme-create",async(req,res)=>{ 94 | await basePicCreateTheme(req,res); 95 | }) 96 | //创建 透明安卓主题 97 | app.post("/attheme-create/tran",async(req,res)=>{ 98 | await basePicCreateTheme(req,res); 99 | }) 100 | //创建桌面主题 101 | app.post("/tdesktop-create",async (req,res)=>{ 102 | await basePicCreateDesktop(req,res); 103 | }) 104 | 105 | //模板信息类 106 | app.get("/templete-info",async (req,res)=>{ 107 | res.end(JSON.stringify(themeInfo)); 108 | }) 109 | 110 | 111 | // 桌面模板应用类 112 | app.post("/templete-editor/",async(req,res)=>{ 113 | let kind=req.body.kind; 114 | let type=req.body.type; 115 | let model=req.body.model; 116 | let targetHue=parseInt(req.body.hue); 117 | let targetS=parseInt(req.body.sat); 118 | let targetL=parseInt(req.body.lig); 119 | let alpha= parseFloat(req.body.alpha); 120 | let tName=`${kind}-${model}-${targetHue}-${targetS}-${targetL}-${alpha}`; 121 | //记录 使用情况 122 | let mysqlLog:theme_editor_logAttributes={ 123 | kind:kind=="android"?0:1, 124 | ip:req.ip, 125 | date:new Date() 126 | } 127 | await db.theme_editor_log.create(mysqlLog) 128 | // log.info(`alpha的值为${alpha}`) 129 | let picBuffer = Buffer.from(dataUriToBuffer(req.body.pic).buffer); 130 | let newVar:any; 131 | res.set({ 132 | 'content-type': 'text/plain', 133 | }) 134 | 135 | if (kind=="android"){ 136 | if (type=="white"){ 137 | newVar = AndroidWhite.androidWhiteMap.get(model); 138 | } 139 | else { 140 | newVar = AndroidBlack.androidBlackMap.get(model); 141 | } 142 | }else { 143 | if (type=="white"){ 144 | newVar = DesktopWhite.desktopWhiteMap.get(model); 145 | } 146 | else { 147 | newVar = DesktopBlack.desktopBlackMap.get(model); 148 | } 149 | } 150 | // console.log(newVar) 151 | //读取模板 152 | //@ts-ignore 153 | let tf=tmp.fileSync(); 154 | let newName= crypto.randomUUID() 155 | if (kind=="android"){ 156 | newName=newName+"L"; 157 | let bu= newVar.translateHue(targetHue,targetS,targetL, picBuffer,alpha) 158 | fs.writeFileSync(tf.fd,bu); 159 | fs.closeSync(tf.fd) 160 | map.set(newName,{tempName:tf.name,themeName:tName}); 161 | res.end(newName); 162 | } 163 | else{ 164 | newVar.translateHue(targetHue,targetS,targetL, picBuffer,alpha).then(e=>{ 165 | newName=newName+"M"; 166 | fs.writeFileSync(tf.fd,e); 167 | map.set(newName,{tempName:tf.name,themeName:tName}); 168 | res.end(newName) 169 | fs.closeSync(tf.fd) 170 | 171 | }) 172 | } 173 | log.info(`收到模板制作主题请求,种类 :${kind}, type:${type}, moudle:${model},图片大小: ${picBuffer.length}KB`); 174 | }) 175 | 176 | let httpsServer:any 177 | if(process.env.NODE_ENV!=="dev"){ 178 | log.info("生产环境启用") 179 | // ip=="167.179.118.142" 180 | const options = { 181 | key: fs.readFileSync('/etc/letsencrypt/live/www.yusme.link/privkey.pem','utf8'), 182 | cert: fs.readFileSync('/etc/letsencrypt/live/www.yusme.link/fullchain.pem','utf8'), 183 | ca: fs.readFileSync('/etc/letsencrypt/live/www.yusme.link/chain.pem', 'utf8') 184 | }; 185 | httpsServer = https.createServer(options, app); 186 | }else { 187 | log.info("开发环境启用") 188 | httpsServer = http.createServer(app); 189 | } 190 | 191 | function initHttp() { 192 | httpsServer.listen(port, () => { 193 | log.info(`https: app 已经运行 端口: ${port}`) 194 | }) 195 | } 196 | export {initHttp} 197 | -------------------------------------------------------------------------------- /src/mianwrapper/templete/base-theme-operation.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import fs from "fs"; 3 | import {NAttheme as Attheme} from "../../lib/wapper/NAttheme"; 4 | import tinycolor from "tinycolor2"; 5 | import path from "path"; 6 | import {Tdesktop as TdesktopTheme} from "../../lib/wapper/NTdesktop"; 7 | import HSLA = tinycolor.ColorFormats.HSLA; 8 | import logger from "../../lib/config/log_config"; 9 | let log=logger.getLogger(`${__filename}`); 10 | 11 | export enum ThemeType { 12 | //单色 13 | Simple , 14 | //单色 但不同 S L 15 | SimpleDifference , 16 | //包括多色 且不同 S L 17 | MultiColor, 18 | } 19 | 20 | // 基本安卓抽象类 21 | export class AnBaseThemeOperation { 22 | protected chatBg="chat_inBubble" 23 | protected chatBgOut="chat_outBubble" 24 | protected prx = "public/tempelete/tohuemodle/android/"; 25 | protected ta:Attheme 26 | protected toHueColor:number 27 | protected targetS:number 28 | protected targetL:number 29 | private mainHSL: tinycolor.ColorFormats.HSLA; 30 | protected constructor( 31 | public type:string, 32 | public id: string, 33 | public tP: string, 34 | public pP: string = "捕获.PNG", // 默认值 35 | public mainColorSelect: string, 36 | public colorType:ThemeType=ThemeType.Simple 37 | ){} 38 | init(){ 39 | this.ta = new Attheme(this.getBuffer()) 40 | let {red:r,green:g,blue:b,alpha:a}= this.ta.get(this.mainColorSelect) 41 | this.mainHSL=tinycolor({r,g,b,a}).toHsl() 42 | } 43 | translateHue(toHueColor:number,targetS:number,targetL:number,background:Buffer,alphaT:number=1){ 44 | this.init() 45 | //为 addTaOpreation添加变量 46 | this.toHueColor=toHueColor 47 | this.targetS=targetS 48 | this.targetL=targetL 49 | // 目标hue 50 | // sideBarBgActive 为主要改变颜色 标准 51 | // @ts-ignore 52 | let {h:mainH,s:mainS}=this.mainHSL 53 | 54 | let en=this.ta.getVariablesList() 55 | log.info(`mainH 为${mainH} 目标H ${toHueColor}`) 56 | 57 | for (const e of en) { 58 | // if(e=="actionBarDefault"){ 59 | // console.log("") 60 | // } 61 | let kp:HSLA; 62 | // @ts-ignore 63 | let {red:r,green:g,blue:b,alpha:a} = this.ta.get(e) 64 | 65 | kp= tinycolor({ r,g,b,a}).toHsl() 66 | let minH=kp.h-mainH 67 | let minS=(kp.s-mainS)*100 68 | // hue 上下 15 69 | if(minH<15 && minH>-15){ 70 | kp.h=toHueColor 71 | if(minS<30 && minS>-30){ 72 | kp.l=targetL/100 73 | } 74 | let {r:red,g:green,b:blue,a:alpha}= tinycolor(kp).toRgb() 75 | this.ta.set(e,{red,green,blue,alpha:a}) 76 | } 77 | } 78 | // console.log(alphaT) 79 | if (alphaT<1){ 80 | // log.info(`${alphaT}`) 81 | // @ts-ignore 82 | let {red:r,green:g,blue:b} = this.ta.get(this.chatBg) 83 | this.ta.set(this.chatBg,{red:r,green:g,blue:b,alpha:Math.floor(alphaT*255)}) 84 | } 85 | this.addTaOperation() 86 | //@ts-ignore 87 | this.ta.setWallpaper(background) 88 | return this.ta.toFile() 89 | } 90 | public addTaOperation(){ 91 | 92 | } 93 | //基本 buffer 获取 94 | private getBuffer(){ 95 | return fs.readFileSync(this.getPath()) 96 | } 97 | private getPath(){ 98 | return path.join(this.prx,this.type,this.id,this.tP) 99 | } 100 | 101 | } 102 | 103 | // 基本桌面抽象类 104 | export class DeBaseThemeOperation { 105 | private chatBg="msgInBg" 106 | private prx = "public/tempelete/tohuemodle/desktop/" 107 | protected mainHSL: tinycolor.ColorFormats.HSLA; 108 | protected tO: any; 109 | protected constructor( 110 | public type: string, 111 | public id: string, 112 | public tP: string, 113 | public pP: string = "捕获.PNG", 114 | public mainColorSelect: string, 115 | public colorType:ThemeType=ThemeType.Simple 116 | ) {} 117 | init(){ 118 | this.tO=new TdesktopTheme(this.getBuffer()) 119 | let {red:r,green:g,blue:b,alpha:a} = this.tO.resolveVariable(this.mainColorSelect) 120 | this.mainHSL=tinycolor({ r,g,b,a}).toHsl() 121 | } 122 | translateHue(toHueColor:number,targetS:number,targetL:number,background:Buffer,alphaT:number=1){ 123 | this.init() 124 | log.info(`mainH 为${this.mainHSL.h} 目标H ${toHueColor}`) 125 | // sideBarBgActive 126 | // 目标hue 为主要改变颜色 标准 127 | //const mianColorSelect="sideBarBgActive" 128 | // @ts-ignore 129 | let {h:mainH,s:mainS}=this.mainHSL 130 | let en=this.tO.entries() 131 | for (const e of en) { 132 | let kp:HSLA; 133 | let ap; 134 | if(typeof e[1]=="string") { 135 | let resolveVariable = this.tO.resolveVariable(e[1]); 136 | if (resolveVariable==null) 137 | continue 138 | //@ts-ignore 139 | let {red:r,green:g,blue:b,alpha:a} = this.tO.resolveVariable(e[1]); 140 | // console.log(`${e[0]}}`); 141 | // console.log(JSON.stringify(); 142 | kp= tinycolor({ r,g,b,a}).toHsl() 143 | ap=a 144 | // @ts-ignore 145 | }else { 146 | let {red:r,green:g,blue:b,alpha:a} = e[1]; 147 | // console.log(`${e[0]}`) 148 | // console.log(JSON.stringify(tinycolor({ r,g,b,a}).toHsv())); 149 | kp=tinycolor({ r,g,b,a}).toHsl() 150 | ap=a 151 | } 152 | let minH=kp.h-mainH 153 | let minS=(kp.s-mainS)*100 154 | // console.log(e[0]) 155 | // console.log(`${kp.s} ${mainS} ${minS} ${targetS}`) 156 | // let minL=(kp.l-mianL)*100 157 | // hue 上下 15 158 | if(minH<15 && minH>-15){ 159 | kp.h=toHueColor 160 | if(minS<30 && minS>-30){ 161 | kp.l=targetL/100 162 | } 163 | // console.log(kp) 164 | let {r:red,g:green,b:blue,a:alpha}= tinycolor(kp).toRgb() 165 | // console.log({red,green,blue,alpha:ap}) 166 | this.tO.setVariable(e[0],{red,green,blue,alpha:ap}) 167 | } 168 | // 目标变量 169 | // 当 H 和目标变量的H相等 并且S接近 差值为30 则修改S=targetS 170 | // L同理 171 | } 172 | if (alphaT<1){ 173 | // @ts-ignore 174 | let {red:r,green:g,blue:b} = this.tO.resolveVariable(this.chatBg) 175 | 176 | this.tO.setVariable(this.chatBg,{red:r,green:g,blue:b,alpha:Math.floor(alphaT*255)}) 177 | } 178 | this.addTaOperation() 179 | // tO.wallpaper.free() 180 | this.tO.backbit=background 181 | //fs.writeFileSync("j.jpg",tO.backbit) 182 | return this.tO.toZip() 183 | } 184 | 185 | protected addTaOperation(){ 186 | 187 | } 188 | 189 | private getBuffer(){ 190 | return fs.readFileSync(this.getPath()) 191 | } 192 | private getPath(){ 193 | return path.join(this.prx,this.type,this.id,this.tP) 194 | } 195 | 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/lib/preview/bot_core.js: -------------------------------------------------------------------------------- 1 | const token = process.env.TOKEN; 2 | 3 | const Telegraf = require(`telegraf`); 4 | const bot = new Telegraf(token); 5 | const request = require(`request-promise`); 6 | const atthemeEditorApi = require(`attheme-editor-api`); 7 | const render = require(`./render-pool`); 8 | const path = require(`path`); 9 | const { 10 | MINIMALISTIC_TEMPLATE,//移动端主题 11 | REGULAR_TEMPLATE, //移动端主题 12 | NEW_TEMPLATE, 13 | DESKTOP_TEMPLATE, //桌面模板 14 | } = require(`./preview-maker`); 15 | const RESEND_ON_ERRORS = [`RequestError`, `FetchError`]; 16 | 17 | const handleRenderError = async (context, error) => { 18 | console.error(error); 19 | if ( 20 | context.updateType == `callback_query` || 21 | context.message.chat.type === `private` 22 | ) { 23 | await context.reply( 24 | `An error happened while making the preview. It may be a problem with your theme, or a bug in the bot. If you think it's the latter, please forward this message to @snejugal. 25 | 26 |
${error}
`, 27 | { parse_mode: `HTML` } 28 | ); 29 | } 30 | }; 31 | 32 | bot.context.downloadFile = async function (fileId) { 33 | if (!fileId) { 34 | fileId = this.callbackQuery.message.reply_to_message.document.file_id; 35 | } 36 | 37 | const file = await bot.telegram.getFile(fileId); 38 | const fileContent = await request({ 39 | encoding: null, 40 | uri: `http://api.telegram.org/file/bot${token}/${file.file_path}`, 41 | }); 42 | 43 | return fileContent; 44 | }; 45 | 46 | const handleStart = async (context) => { 47 | const id = context.message.text.slice(`/start `.length).trim(); 48 | 49 | if (id.length === 0) { 50 | try { 51 | await context.reply( 52 | `Send me an .attheme or a .tdesktop-theme file to create its preview` 53 | ); 54 | return; 55 | } catch (err) { 56 | console.error(err); 57 | return; 58 | } 59 | } 60 | const { name, theme } = await atthemeEditorApi.downloadTheme(id); 61 | let preview; 62 | try { 63 | preview = await render({ 64 | theme, 65 | name, 66 | template: NEW_TEMPLATE, 67 | }); 68 | } catch (error) { 69 | handleRenderError(context, error); 70 | return; 71 | } 72 | 73 | const sendPreview = async () => { 74 | try { 75 | await context.replyWithPhoto( 76 | { source: preview }, 77 | { 78 | reply_to_message_id: context.message.message_id, 79 | caption: `${name}\nCreated by @ThemePreviewBot`, 80 | } 81 | ); 82 | } catch (error) { 83 | if (RESEND_ON_ERRORS.includes(error)) { 84 | process.nextTick(sendPreview); 85 | } else { 86 | console.error(error); 87 | } 88 | } 89 | }; 90 | 91 | sendPreview(); 92 | }; 93 | const choose = async (context) => { 94 | const fileName = context.message.document.file_name; 95 | if (!fileName) { 96 | return; 97 | } 98 | const extenstion = path.extname(fileName); 99 | const name = path.basename(fileName, extenstion); 100 | 101 | if (![`.attheme`, `.tdesktop-theme`].includes(extenstion)) { 102 | return; 103 | } 104 | const isAttheme = extenstion === `.attheme`; 105 | if ( 106 | isAttheme && //如果是移动主题就提供选择 Ordinary 和 Minimalistic 107 | ![`group`, `supergroup`].includes(context.message.chat.type) 108 | ) { 109 | try { 110 | await context.reply(`Select the style`, { 111 | reply_markup: { 112 | inline_keyboard: [ 113 | [ 114 | { text: `Ordinary`, callback_data: `ordinary` }, 115 | { text: `Minimalistic`, callback_data: `minimalistic` }, 116 | ], 117 | ], 118 | }, 119 | reply_to_message_id: context.message.message_id, 120 | }); 121 | return; 122 | } catch (err) { 123 | console.error(err); 124 | return; 125 | } 126 | } 127 | // 不是移动端主题就 直接发送 桌面主题 128 | bot.telegram.sendChatAction(context.message.chat.id, `upload_photo`); 129 | let template = DESKTOP_TEMPLATE; 130 | let reply_markup = {}; 131 | if (isAttheme) { //移动端主题 132 | template = REGULAR_TEMPLATE; 133 | reply_markup = { 134 | inline_keyboard: [ 135 | [{ text: `Minimalistic`, callback_data: `minimalistic` }], 136 | ], 137 | }; 138 | } 139 | const theme = await context.downloadFile(context.message.document.file_id); 140 | 141 | let preview; 142 | try { 143 | preview = await render({ 144 | theme, 145 | name, 146 | template, 147 | }); 148 | } catch (error) { 149 | handleRenderError(context, error); 150 | return; 151 | } 152 | 153 | const sendPreview = async () => { 154 | try { 155 | await context.replyWithPhoto( 156 | { source: preview }, 157 | { 158 | // eslint-disable-next-line camelcase 159 | reply_to_message_id: context.message.message_id, 160 | reply_markup, 161 | caption: isAttheme 162 | ? `Created by @ThemePreviewBot` 163 | : `Design by @voidrainbow`, 164 | } 165 | ); 166 | } catch (error) { 167 | if (RESEND_ON_ERRORS.includes(error.name)) { 168 | process.nextTick(sendPreview); 169 | } else { 170 | console.error(error); 171 | } 172 | } 173 | }; 174 | sendPreview(); 175 | }; 176 | 177 | const handleDocument = async (context) => { 178 | const callbackQuery = context.callbackQuery; 179 | const callbackMessage = callbackQuery.message; 180 | try { 181 | if (callbackMessage.text == `Select the style`) { 182 | await context.deleteMessage(callbackMessage.message_id); 183 | } else { 184 | await bot.telegram.editMessageCaption( 185 | callbackMessage.chat.id, 186 | callbackMessage.message_id, 187 | callbackMessage.message_id, 188 | `Updating the preview...` 189 | ); 190 | } 191 | } catch (err) { 192 | console.error(err); 193 | return; 194 | } 195 | if (!callbackMessage.reply_to_message) return; 196 | try { 197 | await bot.telegram.sendChatAction(callbackMessage.chat.id, `upload_photo`); 198 | } catch (err) { 199 | console.error(err); 200 | return; 201 | } 202 | const fileName = callbackMessage.reply_to_message.document.file_name; 203 | const theme = await context.downloadFile(); 204 | 205 | let preview; 206 | try { 207 | preview = await render({ 208 | theme, 209 | name: fileName.replace(`.attheme`, ``), 210 | template: 211 | callbackQuery.data == `ordinary` 212 | ? REGULAR_TEMPLATE 213 | : MINIMALISTIC_TEMPLATE, 214 | }); 215 | } catch (error) { 216 | handleRenderError(context, error); 217 | return; 218 | } 219 | 220 | const sendPreview = async () => { 221 | const reply_markup = { 222 | inline_keyboard: [ 223 | [ 224 | { 225 | text: 226 | callbackQuery.data === `ordinary` ? `Minimalistic` : `Ordinary`, 227 | callback_data: 228 | callbackQuery.data === `ordinary` ? `minimalistic` : `ordinary`, 229 | }, 230 | ], 231 | ], 232 | }; 233 | const caption = 234 | callbackQuery.data === `minimalistic` 235 | ? `Design by @voidrainbow` 236 | : `Created by @ThemePreviewBot`; 237 | try { 238 | if (callbackMessage.text === `Select the style`) { 239 | await context.replyWithPhoto( 240 | { source: preview }, 241 | { 242 | reply_to_message_id: callbackMessage.reply_to_message.message_id, 243 | reply_markup, 244 | caption, 245 | } 246 | ); 247 | } else { 248 | await context.editMessageMedia( 249 | { type: `photo`, media: { source: preview }, caption }, 250 | { reply_markup } 251 | ); 252 | } 253 | } catch (error) { 254 | if (RESEND_ON_ERRORS.includes(error.name)) { 255 | process.nextTick(sendPreview); 256 | } else { 257 | console.error(error); 258 | } 259 | } 260 | }; 261 | sendPreview(); 262 | }; 263 | 264 | bot.start((context) => { 265 | handleStart(context); 266 | }); 267 | 268 | bot.help((context) => { 269 | context.reply(`Send me an .attheme file to create its preview`); 270 | }); 271 | 272 | bot.on(`document`, (context) => { 273 | choose(context); 274 | }); 275 | 276 | bot.action([`ordinary`, `minimalistic`], (context) => { 277 | handleDocument(context); 278 | }); 279 | 280 | bot.polling.offset = -100; 281 | bot.startPolling(); 282 | -------------------------------------------------------------------------------- /public/assets/colors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/lib/variables/tgios-theme.ts: -------------------------------------------------------------------------------- 1 | import { themeData } from './helpers'; 2 | 3 | const tgiosVariables = (name: string, colors: string[]): string => { 4 | const { 5 | background, 6 | filling, 7 | themeIsLight, 8 | primary, 9 | secondaryText, 10 | text, 11 | textOnPrimary, 12 | bubbleOutColor: outgoingBubbleBackground, 13 | } = themeData(colors); 14 | 15 | const template = ` 16 | name: ${name} 17 | basedOn: day 18 | dark: ${!themeIsLight} 19 | intro: 20 | statusBar: ${themeIsLight ? 'white' : 'black'} 21 | primaryText: ${text} 22 | accentText: ${primary} 23 | disabledText: 33${secondaryText} 24 | startButton: ${primary} 25 | dot: 5e5e5e 26 | passcode: 27 | bg: 28 | top: ${background} 29 | bottom: ${background} 30 | button: clear 31 | root: 32 | statusBar: ${themeIsLight ? 'white' : 'black'} 33 | tabBar: 34 | background: ${background} 35 | separator: 33${primary} 36 | icon: a1${primary} 37 | selectedIcon: ${primary} 38 | text: a1${primary} 39 | selectedText: ${primary} 40 | badgeBackground: ${text} 41 | badgeStroke: ${background} 42 | badgeText: ${background} 43 | navBar: 44 | button: ${primary} 45 | disabledButton: 33${primary} 46 | primaryText: ${text} 47 | secondaryText: ${secondaryText} 48 | control: ${primary} 49 | accentText: ${primary} 50 | background: ${background} 51 | separator: 33${primary} 52 | badgeFill: ${text} 53 | badgeStroke: ${background} 54 | badgeText: ffffff 55 | searchBar: 56 | background: ${background} 57 | accent: ${primary} 58 | inputFill: 33d6d6d6 59 | inputText: 000000 60 | inputPlaceholderText: a1${primary} 61 | inputIcon: 33000000 62 | inputClearButton: 7b7b81 63 | separator: 33${primary} 64 | keyboard: ${themeIsLight ? 'dark' : 'light'} 65 | list: 66 | blocksBg: ${background} 67 | plainBg: ${background} 68 | primaryText: ${text} 69 | secondaryText: ${secondaryText} 70 | disabledText: 1e${primary} 71 | accent: ${primary} 72 | highlighted: 1e${primary} 73 | destructive: ff3b30 74 | placeholderText: c8c8ce 75 | itemBlocksBg: ${background} 76 | itemHighlightedBg: 1e${primary} 77 | blocksSeparator: 33${primary} 78 | plainSeparator: 33${primary} 79 | disclosureArrow: bab9be 80 | sectionHeaderText: 6d6d72 81 | freeText: 6d6d72 82 | freeTextError: cf3030 83 | freeTextSuccess: 26972c 84 | freeMonoIcon: 7e7e87 85 | switch: 86 | frame: e0e0e0 87 | handle: ffffff 88 | content: 77d572 89 | positive: 00c900 90 | negative: ff3b30 91 | disclosureActions: 92 | neutral1: 93 | bg: 4892f2 94 | fg: ffffff 95 | neutral2: 96 | bg: f09a37 97 | fg: ffffff 98 | destructive: 99 | bg: ff3824 100 | fg: ffffff 101 | constructive: 102 | bg: 00c900 103 | fg: ffffff 104 | accent: 105 | bg: ${primary} 106 | fg: ffffff 107 | warning: 108 | bg: ff9500 109 | fg: ffffff 110 | inactive: 111 | bg: bcbcc3 112 | fg: ffffff 113 | check: 114 | bg: ${primary} 115 | stroke: c7c7cc 116 | fg: ffffff 117 | controlSecondary: dedede 118 | freeInputField: 119 | bg: ${background} 120 | stroke: ${background} 121 | placeholder: 96979d 122 | primary: ${text} 123 | control: ${primary} 124 | mediaPlaceholder: e4e4e4 125 | scrollIndicator: 4c000000 126 | pageIndicatorInactive: e3e3e7 127 | inputClearButton: cccccc 128 | chatList: 129 | bg: ${background} 130 | itemSeparator: 33${primary} 131 | itemBg: 1ed6d6d6 132 | pinnedItemBg: ${background} 133 | itemHighlightedBg: 1e${primary} 134 | itemSelectedBg: 1e${primary} 135 | title: ${text} 136 | secretTitle: 00b12c 137 | dateText: ${secondaryText} 138 | authorName: ${secondaryText} 139 | messageText: ${secondaryText} 140 | messageDraftText: ${secondaryText} 141 | checkmark: ${primary} 142 | pendingIndicator: ${secondaryText} 143 | failedFill: ff3b30 144 | failedFg: ffffff 145 | muteIcon: ${secondaryText} 146 | unreadBadgeActiveBg: ${primary} 147 | unreadBadgeActiveText: ${textOnPrimary} 148 | unreadBadgeInactiveBg: b6b6bb 149 | unreadBadgeInactiveText: ffffff 150 | pinnedBadge: ${primary} 151 | pinnedSearchBar: e5e5e5 152 | regularSearchBar: e9e9e9 153 | sectionHeaderBg: 33d6d6d6 154 | sectionHeaderText: 8e8e93 155 | verifiedIconBg: ${primary} 156 | verifiedIconFg: ffffff 157 | secretIcon: 00b12c 158 | pinnedArchiveAvatar: 159 | background: 160 | top: 1e${primary} 161 | bottom: ${primary} 162 | foreground: ffffff 163 | unpinnedArchiveAvatar: 164 | background: 165 | top: 1e${secondaryText} 166 | bottom: ${secondaryText} 167 | foreground: ffffff 168 | onlineDot: 4cc91f 169 | chat: 170 | defaultWallpaper: ${background} 171 | message: 172 | incoming: 173 | bubble: 174 | withWp: 175 | bg: ${filling} 176 | highlightedBg: ccdadade 177 | stroke: ${filling} 178 | withoutWp: 179 | bg: ${filling} 180 | highlightedBg: ccdadade 181 | stroke: ${filling} 182 | primaryText: ${text} 183 | secondaryText: ${secondaryText} 184 | linkText: ${primary} 185 | linkHighlight: 1e${primary} 186 | scam: ff3b30 187 | textHighlight: 1e${primary} 188 | accentText: ${primary} 189 | accentControl: ${primary} 190 | mediaActiveControl: ${primary} 191 | mediaInactiveControl: cacaca 192 | pendingActivity: 99525252 193 | fileTitle: ${primary} 194 | fileDescription: 999999 195 | fileDuration: 99525252 196 | mediaPlaceholder: f2f2f2 197 | polls: 198 | radioButton: c8c7cc 199 | radioProgress: ${primary} 200 | highlight: 1e${primary} 201 | separator: c8c7cc 202 | bar: ${primary} 203 | actionButtonsBg: 204 | withWp: 66a5a5a5 205 | withoutWp: ccffffff 206 | actionButtonsStroke: 207 | withWp: clear 208 | withoutWp: ${primary} 209 | actionButtonsText: 210 | withWp: ffffff 211 | withoutWp: ${primary} 212 | textSelection: 4c${primary} 213 | textSelectionKnob: ${primary} 214 | outgoing: 215 | bubble: 216 | withWp: 217 | bg: ${outgoingBubbleBackground} 218 | highlightedBg: ccdadade 219 | stroke: ${outgoingBubbleBackground} 220 | withoutWp: 221 | bg: ${outgoingBubbleBackground} 222 | highlightedBg: ccdadade 223 | stroke: ${outgoingBubbleBackground} 224 | primaryText: ffffff 225 | secondaryText: a5ffffff 226 | linkText: ffffff 227 | linkHighlight: 4cffffff 228 | scam: ffffff 229 | textHighlight: 4cffffff 230 | accentText: ffffff 231 | accentControl: ffffff 232 | mediaActiveControl: ffffff 233 | mediaInactiveControl: a5ffffff 234 | pendingActivity: a5ffffff 235 | fileTitle: ffffff 236 | fileDescription: a5ffffff 237 | fileDuration: a5ffffff 238 | mediaPlaceholder: 0000f2 239 | polls: 240 | radioButton: a5ffffff 241 | radioProgress: ffffff 242 | highlight: 1effffff 243 | separator: a5ffffff 244 | bar: ffffff 245 | actionButtonsBg: 246 | withWp: 66a5a5a5 247 | withoutWp: ccffffff 248 | actionButtonsStroke: 249 | withWp: clear 250 | withoutWp: ${primary} 251 | actionButtonsText: 252 | withWp: ffffff 253 | withoutWp: ${primary} 254 | textSelection: 33ffffff 255 | textSelectionKnob: ffffff 256 | freeform: 257 | withWp: 258 | bg: e5e5ea 259 | highlightedBg: dadade 260 | stroke: e5e5ea 261 | withoutWp: 262 | bg: e5e5ea 263 | highlightedBg: dadade 264 | stroke: e5e5ea 265 | infoPrimaryText: 000000 266 | infoLinkText: 004bad 267 | outgoingCheck: ffffff 268 | mediaDateAndStatusBg: 7f000000 269 | mediaDateAndStatusText: ffffff 270 | shareButtonBg: 271 | withWp: 66a5a5a5 272 | withoutWp: ccffffff 273 | shareButtonStroke: 274 | withWp: clear 275 | withoutWp: e5e5ea 276 | shareButtonFg: 277 | withWp: ffffff 278 | withoutWp: ${primary} 279 | mediaOverlayControl: 280 | bg: 99000000 281 | fg: ffffff 282 | selectionControl: 283 | bg: ${primary} 284 | stroke: c7c7cc 285 | fg: ffffff 286 | deliveryFailed: 287 | bg: ff3b30 288 | fg: ffffff 289 | mediaHighlightOverlay: 99ffffff 290 | serviceMessage: 291 | components: 292 | withDefaultWp: 293 | bg: ccffffff 294 | primaryText: 8d8e93 295 | linkHighlight: 3f748391 296 | scam: ff3b30 297 | dateFillStatic: ccffffff 298 | dateFillFloat: ccffffff 299 | withCustomWp: 300 | bg: 66a5a5a5 301 | primaryText: ffffff 302 | linkHighlight: 3f748391 303 | scam: ff3b30 304 | dateFillStatic: 66a5a5a5 305 | dateFillFloat: 44a5a5a5 306 | unreadBarBg: ffffff 307 | unreadBarStroke: ffffff 308 | unreadBarText: 8d8e93 309 | dateText: 310 | withWp: ffffff 311 | withoutWp: 8d8e93 312 | inputPanel: 313 | panelBg: ${background} 314 | panelSeparator: 33${primary} 315 | panelControlAccent: ${primary} 316 | panelControl: ${secondaryText} 317 | panelControlDisabled: 1e${secondaryText} 318 | panelControlDestructive: ff3b30 319 | inputBg: 1ed6d6d6 320 | inputStroke: ${background} 321 | inputPlaceholder: 33${secondaryText} 322 | inputText: ${text} 323 | inputControl: ${primary} 324 | actionControlBg: ${primary} 325 | actionControlFg: ffffff 326 | primaryText: ${text} 327 | secondaryText: ${secondaryText} 328 | mediaRecordDot: ${primary} 329 | mediaRecordControl: 330 | button: ${primary} 331 | micLevel: 1e${primary} 332 | activeIcon: ffffff 333 | inputMediaPanel: 334 | panelSeparator: bec2c6 335 | panelIcon: 858e99 336 | panelHighlightedIconBg: 33858e99 337 | stickersBg: e8ebf0 338 | stickersSectionText: 9099a2 339 | stickersSearchBg: d9dbe1 340 | stickersSearchPlaceholder: 8e8e93 341 | stickersSearchPrimary: 000000 342 | stickersSearchControl: 8e8e93 343 | gifsBg: ffffff 344 | inputButtonPanel: 345 | panelBg: dee2e6 346 | panelSeparator: bec2c6 347 | buttonBg: ffffff 348 | buttonStroke: c3c7c9 349 | buttonHighlightedBg: a8b3c0 350 | buttonHighlightedStroke: c3c7c9 351 | buttonText: 000000 352 | historyNav: 353 | bg: ${background} 354 | stroke: ${background} 355 | fg: ${text} 356 | badgeBg: ${primary} 357 | badgeStroke: ${primary} 358 | badgeText: ffffff 359 | actionSheet: 360 | dim: 66000000 361 | bgType: light 362 | opaqueItemBg: ffffff 363 | itemBg: ddffffff 364 | opaqueItemHighlightedBg: e5e5e5 365 | itemHighlightedBg: b2e5e5e5 366 | opaqueItemSeparator: e5e5e5 367 | standardActionText: ${primary} 368 | destructiveActionText: ff3b30 369 | disabledActionText: b3b3b3 370 | primaryText: 000000 371 | secondaryText: 5e5e5e 372 | controlAccent: ${primary} 373 | inputBg: e9e9e9 374 | inputHollowBg: ffffff 375 | inputBorder: e4e4e6 376 | inputPlaceholder: 818086 377 | inputText: 000000 378 | inputClearButton: 7b7b81 379 | checkContent: ffffff 380 | contextMenu: 381 | dim: ${themeIsLight ? '33000a26' : '99000000'} 382 | background: ${themeIsLight ? 'c6f9f9f9' : 'c6252525'} 383 | itemSeparator: ${themeIsLight ? '333c3c43' : '26ffffff'} 384 | sectionSeparator: ${themeIsLight ? '338a8a8a' : '33000000'} 385 | itemBg: ${themeIsLight ? '00000000' : '00000000'} 386 | itemHighlightedBg: ${themeIsLight ? '333c3c43' : '26ffffff'} 387 | primary: ${themeIsLight ? '000000' : 'ffffff'} 388 | secondary: ${themeIsLight ? 'cc000000' : 'ccffffff'} 389 | destructive: ${themeIsLight ? 'ff3b30' : 'eb5545'} 390 | notification: 391 | bg: ffffff 392 | primaryText: 000000 393 | expanded: 394 | bgType: light 395 | navBar: 396 | background: ffffff 397 | primaryText: 000000 398 | control: 7e8791 399 | separator: b1b1b1`; 400 | 401 | return template.replace(/#/g, ''); 402 | }; 403 | 404 | export default tgiosVariables; 405 | -------------------------------------------------------------------------------- /makeWallpaper/Rum Luster.attheme: -------------------------------------------------------------------------------- 1 | wallpaperFileOffset=-1 2 | dialogBackground=-1 3 | dialogBackgroundGray=-1 4 | dialogTextBlack=-12684931 5 | dialogTextLink=-12684931 6 | dialogLinkSelection=842953085 7 | dialogTextBlue=-12684931 8 | dialogTextBlue2=-12684931 9 | dialogTextBlue4=-12684931 10 | dialogTextGray=-12684931 11 | dialogTextGray2=-12684931 12 | dialogInputField=-12684931 13 | dialogInputFieldActivated=-1 14 | dialogCheckboxSquareBackground=-12684931 15 | dialogCheckboxSquareCheck=-1 16 | dialogCheckboxSquareUnchecked=-12684931 17 | dialogCheckboxSquareDisabled=-12684931 18 | dialogScrollGlow=-12684931 19 | dialogRoundCheckBox=-12684931 20 | dialogRoundCheckBoxCheck=-1 21 | dialogRadioBackground=-1 22 | dialogRadioBackgroundChecked=-12684931 23 | dialogLineProgress=-12684931 24 | dialogLineProgressBackground=-6861438 25 | dialogButton=-12684931 26 | dialogButtonSelector=1681813885 27 | dialogIcon=-12684931 28 | dialogGrayLine=-12684931 29 | dialogSearchBackground=1687637378 30 | dialogSearchHint=-6861438 31 | dialogSearchIcon=-6861438 32 | dialogSearchText=-12684931 33 | dialogFloatingButton=-3202 34 | dialogFloatingButtonPressed=-1 35 | dialogFloatingIcon=-1 36 | dialogShadowLine=-6861438 37 | windowBackgroundWhite=-2228228 38 | progressCircle=-12684931 39 | listSelectorSDK21=-12684931 40 | windowBackgroundWhiteGrayIcon=-4356609 41 | windowBackgroundWhiteBlueText=-12684931 42 | windowBackgroundWhiteBlueText3=-12684931 43 | windowBackgroundWhiteBlueText4=-16719410 44 | windowBackgroundWhiteBlueText6=-6861438 45 | windowBackgroundWhiteBlueText7=-6861438 46 | windowBackgroundWhiteGreenText2=-12684931 47 | windowBackgroundWhiteGrayText=-12684931 48 | windowBackgroundWhiteGrayText2=-11036929 49 | windowBackgroundWhiteGrayText3=-12684931 50 | windowBackgroundWhiteGrayText4=-12684931 51 | windowBackgroundWhiteGrayText8=-12684931 52 | windowBackgroundWhiteBlackText=-12684931 53 | windowBackgroundWhiteValueText=-6861438 54 | windowBackgroundWhiteLinkText=-6861438 55 | windowBackgroundWhiteLinkSelection=848776578 56 | windowBackgroundWhiteBlueHeader=-12684931 57 | switchTrack=1268206978 58 | switchTrackChecked=-32036 59 | switch2Track=1268206978 60 | switch2TrackChecked=-6861438 61 | checkboxSquareBackground=-12684931 62 | checkboxSquareCheck=-1 63 | checkboxSquareUnchecked=-12684931 64 | checkboxSquareDisabled=-6861438 65 | windowBackgroundGray=-1 66 | windowBackgroundGrayShadow=0 67 | emptyListPlaceholder=-6861438 68 | divider=4092285 69 | graySection=-1 70 | radioBackground=-12684931 71 | radioBackgroundChecked=-16711697 72 | checkbox=-12684931 73 | checkboxCheck=-1 74 | fastScrollActive=-12684931 75 | fastScrollInactive=-6861438 76 | fastScrollText=-1 77 | inappPlayerPerformer=-12684931 78 | inappPlayerTitle=-12684931 79 | inappPlayerBackground=-1 80 | inappPlayerPlayPause=-12684931 81 | inappPlayerClose=-12684931 82 | returnToCallBackground=-1 83 | returnToCallText=-12684931 84 | contextProgressInner1=-6861438 85 | contextProgressOuter1=-12684931 86 | avatar_text=-1 87 | avatar_backgroundSaved=-8460289 88 | avatar_background2Saved=-12354680 89 | avatar_backgroundArchived=-12684931 90 | avatar_backgroundArchivedHidden=-12684931 91 | avatar_backgroundRed=-12684931 92 | avatar_backgroundOrange=-8460289 93 | avatar_backgroundViolet=-12684931 94 | avatar_backgroundGreen=-8460289 95 | avatar_backgroundCyan=-12684931 96 | avatar_backgroundBlue=-8460289 97 | avatar_backgroundPink=-12684931 98 | avatar_background2Red=-8460289 99 | avatar_background2Orange=-11826793 100 | avatar_background2Violet=-8460289 101 | avatar_background2Green=-12222580 102 | avatar_background2Cyan=-8525825 103 | avatar_background2Blue=-11958636 104 | avatar_background2Pink=-8460289 105 | avatar_backgroundInProfileBlue=-12684931 106 | avatar_backgroundActionBarBlue=-1 107 | avatar_actionBarSelectorBlue=-12684931 108 | avatar_actionBarIconBlue=-12684931 109 | avatar_subtitleInProfileBlue=-12684931 110 | avatar_nameInMessageRed=-12684931 111 | avatar_nameInMessageOrange=-12684931 112 | avatar_nameInMessageViolet=-12684931 113 | avatar_nameInMessageGreen=-12684931 114 | avatar_nameInMessageCyan=-12684931 115 | avatar_nameInMessageBlue=-12684931 116 | avatar_nameInMessagePink=-12684931 117 | actionBarDefault=-1 118 | actionBarDefaultSelector=-12684931 119 | actionBarWhiteSelector=-6861438 120 | actionBarDefaultIcon=-14894337 121 | actionBarActionModeDefault=-1 122 | actionBarActionModeDefaultTop=-1 123 | actionBarActionModeDefaultIcon=-12684931 124 | actionBarActionModeDefaultSelector=-12684931 125 | actionBarDefaultTitle=-12684931 126 | actionBarDefaultSubtitle=-12684931 127 | actionBarDefaultSearch=-12684931 128 | actionBarDefaultSearchPlaceholder=-12684931 129 | actionBarDefaultSubmenuItem=-12684931 130 | actionBarDefaultSubmenuBackground=-1 131 | actionBarTabActiveText=-16263937 132 | actionBarDefaultArchived=-1 133 | actionBarDefaultArchivedSelector=-12684931 134 | actionBarDefaultArchivedIcon=-12684931 135 | actionBarDefaultArchivedTitle=-12684931 136 | actionBarDefaultArchivedSearch=-12684931 137 | actionBarDefaultSearchArchivedPlaceholder=-12684931 138 | chats_unreadCounter=-252091515 139 | chats_unreadCounterMuted=1684321993 140 | chats_unreadCounterText=-1 141 | chats_name=-13775896 142 | chats_nameArchived=-12684931 143 | chats_secretName=-12684931 144 | chats_secretIcon=-12684931 145 | chats_pinnedIcon=-9714985 146 | chats_archiveBackground=-12684931 147 | chats_archivePinBackground=-935431811 148 | chats_archiveIcon=-1 149 | chats_archiveText=-1 150 | chats_message=-11308046 151 | chats_message_threeLines=-65536 152 | chats_draft=-12684931 153 | chats_nameMessage=-2675132 154 | chats_nameMessageArchived=-12684931 155 | chats_nameMessage_threeLines=-12684931 156 | chats_nameMessageArchived_threeLines=-12684931 157 | chats_attachMessage=-6861438 158 | chats_actionMessage=-6861438 159 | chats_date=-15018848 160 | chats_pinnedOverlay=9915778 161 | chats_tabletSelectedOverlay=-12684931 162 | chats_sentCheck=-12684931 163 | chats_sentReadCheck=-5552717 164 | chats_sentClock=-12684931 165 | chats_sentError=-12684931 166 | chats_sentErrorIcon=-1 167 | chats_verifiedBackground=-5009409 168 | chats_verifiedCheck=-1 169 | chats_muteIcon=-12684931 170 | chats_mentionIcon=-1 171 | chats_menuTopShadow=1694498815 172 | chats_menuBackground=-1 173 | chats_menuItemText=-12684931 174 | chats_menuItemCheck=-1 175 | chats_menuItemIcon=-12684931 176 | chats_menuName=-8460289 177 | chats_menuPhone=-12928074 178 | chats_menuPhoneCats=-12684931 179 | chats_actionIcon=-1 180 | chats_actionBackground=-12684931 181 | chats_actionPressedBackground=-12684931 182 | chats_tabUnreadActiveBackground=-65323 183 | chat_attachGalleryBackground=-6861438 184 | chat_attachAudioBackground=-6861438 185 | chat_attachFileBackground=-6861438 186 | chat_attachContactBackground=-6861438 187 | chat_attachLocationBackground=-6861438 188 | chat_inBubble=-1832189954 189 | chat_inBubbleShadow=-6861438 190 | chat_outBubble=-1329757992 191 | chat_outBubbleSelected=-503316481 192 | chat_outBubbleShadow=-6861438 193 | chat_outBubbleGradient=13811455 194 | chat_outSentCheck=-12684931 195 | chat_outSentCheckSelected=-12684931 196 | chat_outSentCheckRead=-8460289 197 | chat_outSentClock=-12684931 198 | chat_outSentClockSelected=-12684931 199 | chat_outViews=-12684931 200 | chat_outViewsSelected=-12684931 201 | chat_outMenu=-12684931 202 | chat_outMenuSelected=-12684931 203 | chat_outInstant=-12684931 204 | chat_outInstantSelected=-12684931 205 | chat_outPreviewInstantText=-12684931 206 | chat_outForwardedNameText=-12684931 207 | chat_outViaBotNameText=-12684931 208 | chat_outReplyLine=-12684931 209 | chat_outReplyNameText=-12684931 210 | chat_outReplyMessageText=-2703690 211 | chat_outReplyMediaMessageText=-12684931 212 | chat_outReplyMediaMessageSelectedText=-12684931 213 | chat_outPreviewLine=-12684931 214 | chat_outSiteNameText=-12684931 215 | chat_outContactNameText=-12684931 216 | chat_outContactPhoneText=-12684931 217 | chat_outAudioPerfomerText=-12684931 218 | chat_outTimeSelectedText=-12684931 219 | chat_outAudioProgress=-1 220 | chat_outAudioSelectedProgress=-1 221 | chat_outTimeText=-45477 222 | chat_outAudioTitleText=-12684931 223 | chat_outAudioDurationText=-12684931 224 | chat_outAudioDurationSelectedText=-12684931 225 | chat_outAudioSeekbar=-6861438 226 | chat_outAudioSeekbarSelected=-6861438 227 | chat_outAudioSeekbarFill=-12684931 228 | chat_outVoiceSeekbar=-13503489 229 | chat_outVoiceSeekbarSelected=-6861438 230 | chat_outVoiceSeekbarFill=-12880996 231 | chat_outFileProgress=-1 232 | chat_outFileProgressSelected=-1 233 | chat_outFileNameText=-12684931 234 | chat_outFileInfoText=-12684931 235 | chat_outFileInfoSelectedText=-12684931 236 | chat_outFileBackground=-1 237 | chat_outFileBackgroundSelected=-1 238 | chat_outVenueInfoText=-6861438 239 | chat_outVenueInfoSelectedText=-6861438 240 | chat_outLoader=-295 241 | chat_outLoaderSelected=-6861438 242 | chat_outLocationIcon=-1 243 | chat_outContactBackground=-12684931 244 | chat_outContactIcon=-1 245 | chat_inBubbleSelected=-503316481 246 | chat_messageTextIn=-13211137 247 | chat_messageTextOut=-16777216 248 | chat_messageLinkIn=-2281669 249 | chat_messageLinkOut=-16773481 250 | chat_serviceText=-12684931 251 | chat_serviceLink=-12684931 252 | chat_serviceIcon=-12684931 253 | chat_serviceBackground=-1258291201 254 | chat_serviceBackgroundSelected=-1258291201 255 | chat_muteIcon=-12684931 256 | chat_lockIcon=-12684931 257 | chat_inSentClock=-12684931 258 | chat_inSentClockSelected=-12684931 259 | chat_inViews=-12684931 260 | chat_inViewsSelected=-12684931 261 | chat_inMenu=-12684931 262 | chat_inMenuSelected=-12684931 263 | chat_inInstant=-12684931 264 | chat_inInstantSelected=-12684931 265 | chat_sentError=-12684931 266 | chat_sentErrorIcon=-1 267 | chat_selectedBackground=1681813885 268 | chat_previewDurationText=-12684931 269 | chat_previewGameText=-12684931 270 | chat_inPreviewInstantText=-12684931 271 | chat_secretTimeText=-12684931 272 | chat_botButtonText=-12684931 273 | chat_inForwardedNameText=-12684931 274 | chat_inViaBotNameText=-12684931 275 | chat_stickerViaBotNameText=-12684931 276 | chat_inReplyLine=-12684931 277 | chat_stickerReplyLine=-12684931 278 | chat_inReplyNameText=-12684931 279 | chat_stickerReplyNameText=-12684931 280 | chat_inReplyMessageText=-831715 281 | chat_inReplyMediaMessageText=-12684931 282 | chat_inReplyMediaMessageSelectedText=-12684931 283 | chat_stickerReplyMessageText=-12684931 284 | chat_inPreviewLine=-12684931 285 | chat_inSiteNameText=-12684931 286 | chat_inContactNameText=-12684931 287 | chat_inContactPhoneText=-12684931 288 | chat_inAudioProgress=-1 289 | chat_inAudioSelectedProgress=-1 290 | chat_mediaTimeText=-1 291 | chat_adminText=-12684931 292 | chat_adminSelectedText=-12684931 293 | chat_inTimeText=-3711033 294 | chat_inTimeSelectedText=-12684931 295 | chat_inAudioPerfomerText=-12684931 296 | chat_inAudioPerfomerSelectedText=-12684931 297 | chat_inAudioTitleText=-12684931 298 | chat_inAudioDurationText=-12684931 299 | chat_inAudioDurationSelectedText=-12684931 300 | chat_inAudioSeekbar=-6861438 301 | chat_inAudioSeekbarSelected=-6861438 302 | chat_inAudioSeekbarFill=-12684931 303 | chat_inVoiceSeekbar=-6861438 304 | chat_inVoiceSeekbarSelected=-6861438 305 | chat_inVoiceSeekbarFill=-12684931 306 | chat_inFileProgress=-1 307 | chat_inFileProgressSelected=-1 308 | chat_inFileNameText=-12684931 309 | chat_inFileInfoText=-12684931 310 | chat_inFileInfoSelectedText=-12684931 311 | chat_inFileBackground=-1 312 | chat_inFileBackgroundSelected=-1 313 | chat_inVenueInfoText=-6861438 314 | chat_inVenueInfoSelectedText=-6861438 315 | chat_linkSelectBackground=842953085 316 | chat_textSelectBackground=1681813885 317 | chat_messagePanelBackground=-1 318 | chat_messagePanelShadow=-1 319 | chat_messagePanelText=-12684931 320 | chat_messagePanelHint=-6861438 321 | chat_messagePanelIcons=-12684931 322 | chat_messagePanelSend=-12684931 323 | key_chat_messagePanelVoiceLock=-12684931 324 | key_chat_messagePanelVoiceLockBackground=-1 325 | key_chat_messagePanelVoiceLockShadow=-1 326 | chat_topPanelBackground=-1 327 | chat_topPanelClose=-12684931 328 | chat_topPanelLine=-12684931 329 | chat_topPanelTitle=-12684931 330 | chat_topPanelMessage=-12684931 331 | chat_addContact=-12684931 332 | chat_inLoader=-12684931 333 | chat_inLoaderSelected=-6861438 334 | chat_inLoaderPhoto=-1 335 | chat_mediaLoaderPhotoIcon=-12684931 336 | chat_mediaLoaderPhotoIconSelected=-12684931 337 | chat_inLocationBackground=-1 338 | chat_inLocationIcon=-1 339 | chat_inContactBackground=-12684931 340 | chat_inContactIcon=-1 341 | chat_replyPanelIcons=-12684931 342 | chat_replyPanelClose=-12684931 343 | chat_replyPanelName=-12684931 344 | chat_replyPanelLine=-12684931 345 | chat_searchPanelIcons=-12684931 346 | chat_searchPanelText=-12684931 347 | chat_fieldOverlayText=-12684931 348 | chat_stickersHintPanel=-1 349 | chat_botSwitchToInlineText=-6861438 350 | chat_unreadMessagesStartArrowIcon=-12684931 351 | chat_unreadMessagesStartText=-12684931 352 | chat_unreadMessagesStartBackground=-1 353 | chat_inlineResultIcon=-12684931 354 | chat_emojiPanelBackground=-1 355 | chat_emojiSearchBackground=-12684931 356 | chat_emojiPanelShadowLine=-1 357 | chat_emojiPanelEmptyText=-12684931 358 | chat_emojiPanelIcon=-6861438 359 | chat_emojiPanelIconSelected=-12684931 360 | chat_emojiPanelStickerPackSelector=1687637378 361 | chat_emojiPanelBackspace=-12684931 362 | chat_emojiPanelTrendingTitle=-12684931 363 | chat_emojiPanelStickerSetName=-6861438 364 | chat_emojiPanelStickerSetNameIcon=-12684931 365 | chat_emojiPanelTrendingDescription=-12684931 366 | chat_botKeyboardButtonText=-1 367 | chat_botKeyboardButtonBackground=-12684931 368 | chat_botKeyboardButtonBackgroundPressed=-6861438 369 | chat_emojiPanelNewTrending=4092285 370 | chat_messagePanelVoicePressed=-12684931 371 | chat_messagePanelVoiceBackground=-1 372 | chat_messagePanelVoiceDelete=-12684931 373 | chat_messagePanelVoiceDuration=-12684931 374 | chat_recordedVoicePlayPause=-12684931 375 | chat_recordedVoiceProgress=-6861438 376 | chat_recordedVoiceProgressInner=-12684931 377 | chat_recordedVoiceDot=-12684931 378 | chat_recordedVoiceBackground=-1 379 | chat_recordVoiceCancel=-12684931 380 | chat_recordTime=-12684931 381 | chat_messagePanelCancelInlineBot=-12684931 382 | chat_goDownButton=-1 383 | chat_goDownButtonIcon=-12684931 384 | chat_goDownButtonCounter=-12684931 385 | chat_goDownButtonCounterBackground=-1 386 | profile_creatorIcon=-12684931 387 | profile_title=-12684931 388 | profile_actionIcon=-12684931 389 | profile_actionBackground=-1 390 | profile_actionPressedBackground=-12684931 391 | profile_verifiedBackground=-12684931 392 | profile_verifiedCheck=-1 393 | sharedMedia_startStopLoadIcon=-12684931 394 | featuredStickers_addedIcon=-12684931 395 | featuredStickers_addButton=-12684931 396 | featuredStickers_addButtonPressed=-6861438 397 | featuredStickers_buttonText=-1 398 | stickers_menu=-12684931 399 | stickers_menuSelector=-12684931 400 | groupcreate_hintText=-12684931 401 | groupcreate_cursor=-12684931 402 | groupcreate_sectionShadow=-1 403 | groupcreate_sectionText=-12684931 404 | groupcreate_spanBackground=-12684931 405 | contacts_inviteBackground=-1 406 | contacts_inviteText=-12684931 407 | picker_enabledButton=-12684931 408 | picker_disabledButton=-6861438 409 | picker_badge=-12684931 410 | picker_badgeText=-1 411 | location_sendLocationBackground=-6861438 412 | location_sendLocationIcon=-1 413 | location_sendLiveLocationBackground=-6861438 414 | location_liveLocationProgress=-6861438 415 | location_placeLocationBackground=-6861438 416 | files_folderIcon=-6861438 417 | files_folderIconBackground=-1 418 | files_iconText=-1 419 | sessions_devicesImage=-6861438 420 | calls_callReceivedGreenIcon=-12684931 421 | calls_callReceivedRedIcon=-12684931 422 | undo_background=-12684931 423 | undo_cancelColor=-1 424 | undo_infoColor=-1 425 | key_sheet_scrollUp=-12684931 426 | key_sheet_other=-12684931 427 | player_actionBarSelector=-12684931 428 | player_actionBarTitle=-12684931 429 | player_actionBarSubtitle=-12684931 430 | player_actionBarItems=-12684931 431 | player_background=-1 432 | player_time=-12684931 433 | player_progressBackground=-6861438 434 | player_progress=-12684931 435 | player_button=-12684931 436 | player_buttonActive=-6861438 437 | WLS=https://attheme.org?slug=1OCF83C_KVVoAgAAuPCP5L6oXSU 438 | --------------------------------------------------------------------------------