├── .gitattributes ├── .gitignore ├── .editorconfig ├── src ├── meta.txt ├── vencord-native.ts ├── bd-main.js ├── vencord-main.js ├── Button.svelte ├── styles │ └── main.scss └── App.svelte ├── dist └── maganevencord │ └── native.ts ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── deploy.yml ├── LICENSE ├── package.json ├── README_ja.md ├── README.md ├── rollup-bd.config.js └── rollup-vencord.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist-dev/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | insert_final_newline = true 15 | trim_trailing_whitespace = false 16 | 17 | [*.json] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /src/meta.txt: -------------------------------------------------------------------------------- 1 | @name <%= data.name %> 2 | @displayName <%= data.displayName %> 3 | @description <%= data.description %> 4 | @author Kana, Bobby 5 | @authorId 176200089226706944 6 | @authorLink https://github.com/Pitu 7 | @license MIT - https://opensource.org/licenses/MIT 8 | @version <%= data.version %> 9 | @invite 5g6vgwn 10 | @source https://github.com/Pitu/Magane 11 | @updateUrl <%= data.updateUrl %> 12 | @website https://magane.moe 13 | @donate https://github.com/sponsors/Pitu 14 | @patreon https://patreon.com/pitu 15 | -------------------------------------------------------------------------------- /src/vencord-native.ts: -------------------------------------------------------------------------------- 1 | import { CspPolicies, ImageSrc } from '@main/csp'; 2 | 3 | // Magane's built-in packs 4 | CspPolicies['magane.moe'] = ImageSrc; 5 | // Chibisafe 6 | CspPolicies['chibisafe.moe'] = ImageSrc; 7 | // LINE Store 8 | CspPolicies['stickershop.line-scdn.net'] = ImageSrc; 9 | // Image resize service for imported LINE Store packs 10 | CspPolicies['wsrv.nl'] = ImageSrc; 11 | 12 | // If you need to import packs from third-party Chibisafe-based hosts, add the domains here. 13 | // CspPolicies['example.com'] = ImageSrc; 14 | -------------------------------------------------------------------------------- /dist/maganevencord/native.ts: -------------------------------------------------------------------------------- 1 | import { CspPolicies, ImageSrc } from '@main/csp'; 2 | 3 | // Magane's built-in packs 4 | CspPolicies['magane.moe'] = ImageSrc; 5 | // Chibisafe 6 | CspPolicies['chibisafe.moe'] = ImageSrc; 7 | // LINE Store 8 | CspPolicies['stickershop.line-scdn.net'] = ImageSrc; 9 | // Image resize service for imported LINE Store packs 10 | CspPolicies['wsrv.nl'] = ImageSrc; 11 | 12 | // If you need to import packs from third-party Chibisafe-based hosts, add the domains here. 13 | // CspPolicies['example.com'] = ImageSrc; 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': 'aqua/svelte', 3 | 'env': { 4 | browser: true, 5 | node: true, 6 | es2021: true 7 | }, 8 | 'rules': { 9 | 'comma-dangle': ['error', 'never'], 10 | 'import/order': 'off', 11 | 'semi': 'error' 12 | }, 13 | 'overrides': [ 14 | { 15 | files: [ 16 | '*.config.js' 17 | ], 18 | env: { 19 | browser: false 20 | } 21 | } 22 | ], 23 | 'ignorePatterns': [ 24 | 'dist/', 25 | 'dist-dev/' 26 | ], 27 | 'settings': { 28 | 'svelte3/ignore-warnings': ({ code }) => code === 'a11y-click-events-have-key-events' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: pitu 4 | patreon: pitu 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pitu 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/bd-main.js: -------------------------------------------------------------------------------- 1 | /* global BdApi */ 2 | const App = require('./App.svelte'); 3 | 4 | module.exports = class MaganeBD { 5 | log(message, type = 'log') { 6 | return console[type]('%c[MaganeBD]%c', 'color: #3a71c1; font-weight: 700', '', message); 7 | } 8 | 9 | load() {} 10 | 11 | start() { 12 | for (const id of Object.keys(global.MAGANE_STYLES)) { 13 | const _id = `${this.constructor.name}-${id}`; 14 | BdApi.injectCSS(_id, global.MAGANE_STYLES[id]); 15 | this.log(`Injected CSS with ID "${_id}".`); 16 | } 17 | this.log('Mounting container into DOM\u2026'); 18 | this.container = document.createElement('div'); 19 | this.container.id = 'maganeContainer'; 20 | document.body.appendChild(this.container); 21 | this.app = new App({ 22 | target: this.container 23 | }); 24 | } 25 | 26 | stop() { 27 | if (this.app) { 28 | this.app.$destroy(); 29 | this.log('Destroyed Svelte component.'); 30 | } 31 | if (this.container) { 32 | this.container.remove(); 33 | this.log('Removed container from DOM.'); 34 | } 35 | for (const id of Object.keys(global.MAGANE_STYLES)) { 36 | const _id = `${this.constructor.name}-${id}`; 37 | BdApi.clearCSS(); 38 | this.log(`Cleared CSS with ID "${_id}".`); 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magane", 3 | "version": "3.2.23", 4 | "description": "A lie about a lie... It turns inside-out on itself.", 5 | "author": "Pitu ", 6 | "license": "MIT", 7 | "contributors": [ 8 | { 9 | "name": "iCrawl", 10 | "email": "icrawltogo@gmail.com" 11 | }, 12 | { 13 | "name": "BobbyWibowo", 14 | "email": "bobby@fiery.me" 15 | } 16 | ], 17 | "engines": { 18 | "node": ">=18" 19 | }, 20 | "scripts": { 21 | "build-bd": "rollup -c rollup-bd.config.js", 22 | "dev-bd": "rollup -c rollup-bd.config.js -w", 23 | "build-vc": "rollup -c rollup-vencord.config.js", 24 | "dev-vc": "rollup -c rollup-vencord.config.js -w" 25 | }, 26 | "dependencies": {}, 27 | "devDependencies": { 28 | "@rollup/plugin-commonjs": "^11.1.0", 29 | "@rollup/plugin-node-resolve": "^11.2.1", 30 | "@rollup/plugin-replace": "^5.0.2", 31 | "@rollup/plugin-terser": "^0.4.4", 32 | "eslint": "^8.57.0", 33 | "eslint-config-aqua": "^9.2.1", 34 | "eslint-plugin-import": "^2.29.1", 35 | "eslint-plugin-svelte3": "^4.0.0", 36 | "postcss-preset-env": "^6.7.0", 37 | "rollup": "^1.32.1", 38 | "rollup-plugin-license": "^2.8.1", 39 | "rollup-plugin-livereload": "^1.0.0", 40 | "rollup-plugin-postcss": "^v2.8.2", 41 | "rollup-plugin-serve": "^1.1.0", 42 | "rollup-plugin-svelte": "^6.1.1", 43 | "sass": "^1.52.3", 44 | "semver": "^7.5.3", 45 | "svelte": "^3.48.0", 46 | "svelte-preprocess": "^4.10.7", 47 | "svelte-scrollto": "^0.2.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/vencord-main.js: -------------------------------------------------------------------------------- 1 | /* global definePlugin */ 2 | const App = require('./App.svelte'); 3 | 4 | module.exports = definePlugin({ 5 | name: 'MaganeVencord', 6 | authors: [ 7 | { 8 | id: 176200089226706944n, 9 | name: 'Pitu' 10 | }, 11 | { 12 | id: 530445553562025984n, 13 | name: 'Bobby' 14 | } 15 | ], 16 | description: 'Bringing LINE stickers to Discord in a chaotic way. Vencord edition.', 17 | 18 | log(message, type = 'log') { 19 | return console[type]('%c[MaganeVencord]%c', 'color: #3a71c1; font-weight: 700', '', message); 20 | }, 21 | 22 | start() { 23 | for (const id of Object.keys(window.MAGANE_STYLES)) { 24 | const _id = `MaganeVencord-${id}`; 25 | const style = document.createElement('style'); 26 | style.id = _id; 27 | style.innerText = window.MAGANE_STYLES[id]; 28 | document.head.appendChild(style); 29 | this.log(`Injected CSS with ID "${_id}".`); 30 | } 31 | this.log('Mounting container into DOM\u2026'); 32 | this.container = document.createElement('div'); 33 | this.container.id = 'maganeContainer'; 34 | document.body.appendChild(this.container); 35 | this.app = new App({ 36 | target: this.container 37 | }); 38 | }, 39 | 40 | stop() { 41 | if (this.app) { 42 | this.app.$destroy(); 43 | this.log('Destroyed Svelte component.'); 44 | } 45 | if (this.container) { 46 | this.container.remove(); 47 | this.log('Removed container from DOM.'); 48 | } 49 | for (const id of Object.keys(window.MAGANE_STYLES)) { 50 | const _id = `MaganeVencord-${id}`; 51 | const _style = document.querySelector(`head style#${_id}`); 52 | if (_style) { 53 | _style.remove(); 54 | this.log(`Cleared CSS with ID "${_id}".`); 55 | } 56 | } 57 | } 58 | }); 59 | -------------------------------------------------------------------------------- /src/Button.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 |
30 | Magane menu button 31 |
32 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Release 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | env: 15 | NODE_VERSION: 18 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a single job called "build" 20 | build: 21 | # The type of runner that the job will run on 22 | runs-on: ubuntu-latest 23 | 24 | # Steps represent a sequence of tasks that will be executed as part of the job 25 | steps: 26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 27 | - name: Checkout repo 28 | uses: actions/checkout@v4 29 | 30 | - name: Install Node v14 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ env.NODE_VERSION }} 34 | 35 | - name: Cache node_modules 36 | uses: actions/cache@v4 37 | id: cache-nodemodules 38 | with: 39 | path: node_modules 40 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 41 | 42 | - name: Install dependencies 43 | if: steps.cache-nodemodules.outputs.cache-hit != 'true' 44 | run: yarn install --frozen-lockfile --non-interactive 45 | 46 | - name: Update browserslist/caniuse-lite 47 | run: npx browserslist@latest --update-db 48 | 49 | - name: Build application (BetterDiscord plugin) 50 | run: yarn build-bd 51 | 52 | - name: Build application (Vencord plugin) 53 | run: yarn build-vc 54 | 55 | - uses: EndBug/add-and-commit@v6 56 | with: 57 | branch: master 58 | message: 'dist: deploy new dist file' 59 | -------------------------------------------------------------------------------- /README_ja.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE) 6 | [![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn) 7 | [![Support me](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fpitu&style=flat-square)](https://www.patreon.com/pitu) 8 | [![Support me](https://img.shields.io/badge/Support-Buy%20me%20a%20coffee-yellow.svg?style=flat-square)](https://www.buymeacoffee.com/kana) 9 | 10 | ### これは何? 11 | 12 | LINE や Telegram のように、ワンクリックでさまざまなリアクションを送信できる素敵なスタンプ機能がありますよね? 13 | これは Discord 上でまさにそれを実現するための新しいアプローチです! 14 | 15 | [こんな感じです!](https://chibisafe.moe/owdxQF9m.mp4) 16 | 17 | ### どうやって動くの? 18 | 19 | このスクリプトを Discord に注入することで、[chibisafe](https://chibisafe.moe) から厳選されたスタンパックを自動的に取得し、あなたの Discord クライアントに追加します。その後、メッセージバーにハートのボタンが表示され、すべてのスタンプを確認できるようになります! 20 | 21 | ### 永続的に使えるの? 22 | 23 | もちろん可能です! 24 | 25 | 現在、この機能を有効にする最も簡単な方法は [BetterDiscord](https://github.com/rauenzi/BetterDiscordApp/releases) か [Vencord](https://vencord.dev/) を使うことです。 26 | 27 | #### BetterDiscord の場合 28 | 29 | - [https://magane.moe/api/dist/betterdiscord](https://magane.moe/api/dist/betterdiscord) から Magane の BetterDiscord プラグインファイルをダウンロードします。 30 | - プラグインファイルを BetterDiscord の **plugins** ディレクトリに配置します。 31 | - Discord の **ユーザー設定 > BetterDiscord > プラグイン** から Magane を有効にします。 32 | 33 | #### Vencord の場合 34 | 35 | 1. [https://github.com/Pitu/Magane/tree/master/dist/maganevencord](https://github.com/Pitu/Magane/tree/master/dist/maganevencord) から Magane の Vencord プラグインディレクトリを取得します。 36 | 2. ソースから Vencord をインストールします。インストール手順は [https://docs.vencord.dev/installing/](https://docs.vencord.dev/installing/) を参照してください。 37 | 3. Vencord の git ディレクトリで `src` に移動し、`userplugins` ディレクトリを作成します(例: /path/to/vencord/src/userplugins)。 38 | 4. その中に Magane のプラグインディレクトリを配置します(例: .../userplugins/maganevencord)。`index.ts` と `native.ts` の両方が含まれている必要があります。 39 | 5. Vencord を再ビルドします(`pnpm build`)。 40 | 6. (オプション)未実施であれば、Discord に Vencord を注入する必要があります(`pnpm inject`)。 41 | **Vesktop** を使用している場合は、下記を参照してください。 42 | 7. Discord / Vesktop を再起動します。 43 | 8. Discord の **ユーザー設定 > Vencord > プラグイン** から Magane を有効にします。 44 | 45 | > Vencord は CSP を強制するため、サードパーティの Chibisafe ホストからパックをインポートしたい場合は、`native.ts` ファイルにドメインを追加する必要があります。 46 | > 変更後は Discord / Vesktop を再起動してください。 47 | 48 | #### Vesktop の場合 49 | 50 | 上記の手順のステップ 6 まで同様に実施します。 51 | 52 | その後、Vesktop 内の **ユーザー設定 > Vencord > Vesktop 設定 > 開発者設定を開く > Vencord の場所を開く** に進みます。 53 | Vencord git のローカルコピー内の **dist** サブディレクトリを指定してください [(プレビュー)](https://chibisafe.moe/pCX4Qa82.png)。その後、Vesktop を再起動します。 54 | 55 | これで Vesktop の **プラグイン** 設定から Magane を有効にできるようになります。 56 | 57 | ### スクリプトを追加したあとの使い方は? 58 | 59 | とても簡単です。 60 | スタンプポップアップを開いたら、右上の小さなツールアイコンをクリックしてください。利用可能なスタンパックの一覧が表示されます。 61 | スタンパックをクリックすると、それに購読され、そのパック内のすべてのスタンプがメイン画面で使用可能になります。 62 | スタンプを右クリックするとお気に入りに追加できます。再度右クリックすることで削除できます。 63 | 64 | もう一度 [プレビュー動画](https://chibisafe.moe/owdxQF9m.mp4) をご覧ください。 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE) 6 | [![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn) 7 | [![Support me](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fpitu&style=flat-square)](https://www.patreon.com/pitu) 8 | [![Support me](https://img.shields.io/badge/Support-Buy%20me%20a%20coffee-yellow.svg?style=flat-square)](https://www.buymeacoffee.com/kana) 9 | 10 | ### What does this do? 11 | 12 | You know how LINE and Telegram have this beautiful sticker system where you can send different reactions with just one click? This is a new approach at doing exactly that but on Discord! 13 | 14 | [It looks like this!](https://chibisafe.moe/owdxQF9m.mp4) 15 | 16 | ### How even? 17 | 18 | By injecting the script into Discord it will automatically pull a curated list of sticker packs from [chibisafe](https://chibisafe.moe) and add them to your Discord client. After that a heart button will appear in your message bar where you'll be able to see all the stickers! 19 | 20 | 21 | ### Can I make this permanent? 22 | 23 | You can! 24 | 25 | Currently the best way to get this up and running is by using [BetterDiscord](https://github.com/rauenzi/BetterDiscordApp/releases) or [Vencord](https://vencord.dev/). 26 | 27 | #### BetterDiscord 28 | 29 | - Grab Magane's BetterDiscord plugin file from [https://magane.moe/api/dist/betterdiscord](https://magane.moe/api/dist/betterdiscord). 30 | - Place the plugin file into your BetterDiscord's **plugins** directory. 31 | - Activate Magane from Discord's **User Settings > BetterDiscord > Plugins**. 32 | 33 | #### Vencord 34 | 35 | 1. Grab Magane's Vencord plugin directory from [https://github.com/Pitu/Magane/tree/master/dist/maganevencord](https://github.com/Pitu/Magane/tree/master/dist/maganevencord). 36 | 2. Install Vencord from source. Follow its installation guide at [https://docs.vencord.dev/installing/](https://docs.vencord.dev/installing/). 37 | 3. In Vencord git directory, navigate to `src`, then create `userplugins` directory (e.g., `/path/to/vencord/src/userplugins`). 38 | 4. Place Magane's plugin directory into it (e.g, `.../userplugins/maganevencord`). It must contain both `index.ts` and `native.ts`. 39 | 5. Rebuild Vencord (`pnpm build`). 40 | 6. (optional) If you had not done it before, you must inject Vencord into your Discord installation (`pnpm inject`). 41 | For those that use **Vesktop**, please see below. 42 | 7. Restart your Discord/Vesktop. 43 | 8. Activate Magane from Discord's **User Settings > Vencord > Plugins**. 44 | 45 | > Due to Vencord enforcing CSP, if you need to import packs from third-party Chibisafe hosts, you have to add the domains to `native.ts` file. 46 | > Restart your Discord/Vesktop after making any changes to it. 47 | 48 | #### Vesktop 49 | 50 | Follow the same steps as above, until step 6. 51 | 52 | Then in your Vesktop, navigate to Discord's **User Settings > Vencord > Vesktop Settings > Open Developer Settings > Vencord Location**. Change its directory to the **dist** sub-directory of your local copy of Vencord git [(preview)](https://chibisafe.moe/pCX4Qa82.png). Then restart Vesktop. 53 | 54 | You should now be able to activate Magane from Vesktop's **Plugins** settings. 55 | 56 | ### So how exactly do I use it after adding the script? 57 | 58 | It's very easy. 59 | After opening the sticker popup click on the little tool icon and you'll see a list of available sticker packs. Clicking on a sticker pack will subscribe you to it, making every sticker on that pack available on the main window. You can also right click any sticker in the list and add them to your favorites for easy access! To remove one, right click on it again. 60 | 61 | Or watch [the preview](https://chibisafe.moe/owdxQF9m.mp4) again. 62 | -------------------------------------------------------------------------------- /rollup-bd.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import license from 'rollup-plugin-license'; 5 | import terser from '@rollup/plugin-terser'; 6 | import replace from '@rollup/plugin-replace'; 7 | import postcss from 'rollup-plugin-postcss'; 8 | import postcssPresetEnv from 'postcss-preset-env'; 9 | import autoPreprocess from 'svelte-preprocess'; 10 | import fs from 'fs/promises'; 11 | import path from 'path'; 12 | 13 | const production = !process.env.ROLLUP_WATCH; 14 | const metadata = { 15 | name: 'MaganeBD', 16 | displayName: 'MaganeBD', 17 | description: 'Bringing LINE stickers to Discord in a chaotic way. BetterDiscord edition.', 18 | version: require('./package.json').version, 19 | updateUrl: 'https://raw.githubusercontent.com/Pitu/Magane/master/dist/magane.plugin.js' 20 | }; 21 | 22 | const PACKAGE_URL = 'https://raw.githubusercontent.com/Pitu/Magane/refs/heads/master/package.json'; 23 | const GITHUB_URL = 'https://github.com/Pitu/Magane'; 24 | 25 | const meta = path.resolve(__dirname, 'src/meta.txt'); 26 | 27 | const dist = path.resolve(__dirname, production ? 'dist' : 'dist-dev'); 28 | const outputFile = path.resolve(dist, 'magane.plugin.js'); 29 | 30 | export default { 31 | input: 'src/bd-main.js', 32 | output: { 33 | file: outputFile, 34 | format: 'cjs', 35 | name: 'app', 36 | // BetterDiscord won't make sourcemaps available to DevTools anyways, 37 | // and we are not minifying processed output to follow BetterDiscord guidelines, 38 | // so might as well not generate them altogether. 39 | sourcemap: false 40 | }, 41 | plugins: [ 42 | !production && { 43 | name: 'watchExtras', 44 | buildStart() { 45 | this.addWatchFile(meta); 46 | } 47 | }, 48 | svelte({ 49 | dev: !production, 50 | emitCss: true, 51 | preprocess: autoPreprocess({ 52 | replace: [ 53 | ['const VERSION = null;', `const VERSION = '${metadata.version}';`], 54 | ['const UPDATE_URL = null;', `const UPDATE_URL = '${metadata.updateUrl}';`], 55 | ['const PACKAGE_URL = null;', `const PACKAGE_URL = '${PACKAGE_URL}';`], 56 | ['const GITHUB_URL = null;', `const GITHUB_URL = '${GITHUB_URL}';`] 57 | ] 58 | }), 59 | onwarn: (warning, handler) => { 60 | if (warning.code === 'a11y-click-events-have-key-events') return; 61 | handler(warning); 62 | } 63 | }), 64 | postcss({ 65 | extensions: ['.css', '.scss'], 66 | plugins: [ 67 | postcssPresetEnv() 68 | ], 69 | inject: (cssVariableName, fileId) => { 70 | // Extract packaga name if available 71 | const match = fileId.match(/[\/]node_modules[\/](.*?)[\/]/); 72 | let pkg = ''; 73 | if (match) pkg = `${match[1]}-`; 74 | // Normalize basename 75 | const id = pkg + path.basename(fileId).replace(/\./, '_'); 76 | // Arguably hacky.., but cleanest method that I could think of 77 | return 'if (typeof window.MAGANE_STYLES !== "object") window.MAGANE_STYLES = {};\n' + 78 | `window.MAGANE_STYLES["${id}"] = ${cssVariableName};`; 79 | } 80 | }), 81 | resolve({ 82 | browser: true 83 | }), 84 | commonjs(), 85 | production && terser({ 86 | ecma: 2021, 87 | compress: { 88 | keep_classnames: true, 89 | keep_fnames: true, 90 | passes: 1 91 | }, 92 | mangle: false, 93 | output: { 94 | beautify: true, 95 | keep_numbers: true, 96 | indent_level: 4 97 | } 98 | }), 99 | production && replace({ 100 | delimiters: ['', ''], 101 | preventAssignment: false, 102 | values: { 103 | ' ': '\t' 104 | } 105 | }), 106 | license({ 107 | banner: { 108 | commentStyle: 'regular', 109 | content: { 110 | file: meta, 111 | encoding: 'utf-8' 112 | }, 113 | data() { return metadata; } 114 | } 115 | }), 116 | { 117 | name: 'copyDistFile', 118 | writeBundle: async () => { 119 | if (!Boolean(process.env.BD_PLUGIN_PATH)) return; 120 | await fs.copyFile(outputFile, process.env.BD_PLUGIN_PATH); 121 | console.log(`Copied dist file to ${process.env.BD_PLUGIN_PATH}`); 122 | } 123 | } 124 | ], 125 | watch: { 126 | clearScreen: false 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /rollup-vencord.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import license from 'rollup-plugin-license'; 5 | import terser from '@rollup/plugin-terser'; 6 | import replace from '@rollup/plugin-replace'; 7 | import postcss from 'rollup-plugin-postcss'; 8 | import postcssPresetEnv from 'postcss-preset-env'; 9 | import autoPreprocess from 'svelte-preprocess'; 10 | import fs from 'fs/promises'; 11 | import path from 'path'; 12 | 13 | const production = !process.env.ROLLUP_WATCH; 14 | const metadata = { 15 | name: 'MaganeVencord', 16 | displayName: 'MaganeVencord', 17 | description: 'Bringing LINE stickers to Discord in a chaotic way. Vencord edition.', 18 | version: require('./package.json').version, 19 | updateUrl: 'https://raw.githubusercontent.com/Pitu/Magane/master/dist/maganevencord' 20 | }; 21 | 22 | const PACKAGE_URL = 'https://raw.githubusercontent.com/Pitu/Magane/refs/heads/master/package.json'; 23 | const GITHUB_URL = 'https://github.com/Pitu/Magane'; 24 | 25 | const nativeFile = path.resolve(__dirname, 'src/vencord-native.ts'); 26 | const meta = path.resolve(__dirname, 'src/meta.txt'); 27 | 28 | const dist = path.resolve(__dirname, production ? 'dist' : 'dist-dev', 'maganevencord'); 29 | const outputFileName = 'index.ts'; 30 | const outputFile = path.resolve(dist, outputFileName); 31 | 32 | export default { 33 | input: 'src/vencord-main.js', 34 | output: { 35 | file: outputFile, 36 | format: 'cjs', 37 | name: 'app', 38 | // BetterDiscord won't make sourcemaps available to DevTools anyways, 39 | // and we are not minifying processed output to follow BetterDiscord guidelines, 40 | // so might as well not generate them altogether. 41 | sourcemap: false 42 | }, 43 | plugins: [ 44 | { 45 | name: 'init', 46 | buildStart() { 47 | if (process.env.VENCORD_PLUGIN_PATH && /\.(js|ts$)/i.test(process.env.VENCORD_PLUGIN_PATH)) { 48 | throw new Error('VENCORD_PLUGIN_PATH requires directory path. e.g., /path/to/Vencord/src/userplugins/maganevencord.'); 49 | } 50 | } 51 | }, 52 | !production && { 53 | name: 'watchExtras', 54 | buildStart() { 55 | this.addWatchFile(meta); 56 | this.addWatchFile(nativeFile); 57 | } 58 | }, 59 | svelte({ 60 | dev: !production, 61 | emitCss: true, 62 | preprocess: autoPreprocess({ 63 | replace: [ 64 | [/VencordApi\./g, ''], 65 | ['mountType = mountType;', 'mountType = MountType.VENCORD;'], 66 | ['const VERSION = null;', `const VERSION = '${metadata.version}';`], 67 | ['const UPDATE_URL = null;', `const UPDATE_URL = '${metadata.updateUrl}';`], 68 | ['const PACKAGE_URL = null;', `const PACKAGE_URL = '${PACKAGE_URL}';`], 69 | ['const GITHUB_URL = null;', `const GITHUB_URL = '${GITHUB_URL}';`] 70 | ] 71 | }), 72 | onwarn: (warning, handler) => { 73 | if (warning.code === 'a11y-click-events-have-key-events') return; 74 | handler(warning); 75 | } 76 | }), 77 | postcss({ 78 | extensions: ['.css', '.scss'], 79 | plugins: [ 80 | postcssPresetEnv() 81 | ], 82 | inject: (cssVariableName, fileId) => { 83 | // Extract packaga name if available 84 | const match = fileId.match(/[\/]node_modules[\/](.*?)[\/]/); 85 | let pkg = ''; 86 | if (match) pkg = `${match[1]}-`; 87 | // Normalize basename 88 | const id = pkg + path.basename(fileId).replace(/\./, '_'); 89 | // Arguably hacky.., but cleanest method that I could think of 90 | return 'if (typeof window.MAGANE_STYLES !== "object") window.MAGANE_STYLES = {};\n' + 91 | `window.MAGANE_STYLES["${id}"] = ${cssVariableName};`; 92 | } 93 | }), 94 | resolve({ 95 | browser: true 96 | }), 97 | commonjs(), 98 | production && terser({ 99 | ecma: 2021, 100 | compress: { 101 | keep_classnames: true, 102 | keep_fnames: true, 103 | passes: 1 104 | }, 105 | mangle: false, 106 | output: { 107 | beautify: true, 108 | keep_numbers: true, 109 | indent_level: 4 110 | } 111 | }), 112 | production && replace({ 113 | delimiters: ['', ''], 114 | preventAssignment: false, 115 | values: { 116 | ' ': '\t' 117 | } 118 | }), 119 | license({ 120 | banner: { 121 | commentStyle: 'regular', 122 | content: { 123 | file: meta, 124 | encoding: 'utf-8' 125 | }, 126 | data() { return metadata; } 127 | } 128 | }), 129 | { 130 | name: 'vencordImports', 131 | generateBundle: (options, bundle, isWrite) => { 132 | bundle[outputFileName].code = bundle[outputFileName].code.replace( 133 | /(['"]use strict['"];)/, 134 | '$1\n\n' + 135 | 'import definePlugin from "@utils/types";\n' + 136 | 'import { findByPropsLazy, findLazy } from "@webpack";\n' + 137 | 'import { Alerts, Toasts } from "@webpack/common";\n' + 138 | 'import { Notices } from "@api/index";' 139 | ); 140 | } 141 | }, 142 | { 143 | name: 'copyDistFile', 144 | writeBundle: async () => { 145 | // Copy native file to dist directory. 146 | fs.copyFile(nativeFile, path.resolve(dist, 'native.ts')); 147 | 148 | if (!Boolean(process.env.VENCORD_PLUGIN_PATH)) return; 149 | 150 | fs.mkdir(process.env.VENCORD_PLUGIN_PATH, { recursive: true }); 151 | 152 | const indexDest = path.resolve(process.env.VENCORD_PLUGIN_PATH, 'index.ts'); 153 | await fs.copyFile(outputFile, indexDest); 154 | console.log(`Copied index file to ${indexDest}`); 155 | 156 | const nativeDest = path.resolve(process.env.VENCORD_PLUGIN_PATH, 'native.ts'); 157 | await fs.copyFile(nativeFile, nativeDest); 158 | console.log(`Copied native file to ${nativeDest}`); 159 | } 160 | } 161 | ], 162 | watch: { 163 | clearScreen: false 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | /** Magane: main.scss **/ 2 | 3 | div#magane { 4 | $backgroundPrimary: var(--background-base-low, #202024); 5 | $backgroundSecondary: var(--background-base-lower, #1a1a1e); 6 | $textColor: var(--header-secondary, #efeff0); 7 | $hoverColor: var(--interactive-active, #fbfbfb); 8 | $scrollbarColor: rgb(105, 96, 128); 9 | display: flex; 10 | flex-direction: row; 11 | height: 44px; 12 | position: absolute; 13 | z-index: 1001; 14 | 15 | button, input, select, label, span, p, a, li, ul, div, textarea { 16 | font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 17 | color: $textColor; 18 | font-weight: 400; 19 | line-height: 1.5; 20 | font-size: 16px; 21 | text-rendering: optimizeLegibility; 22 | text-size-adjust: 100%; 23 | } 24 | 25 | div.stickerWindow { 26 | width: 600px; 27 | min-height: 200px; 28 | position: fixed; 29 | background: $backgroundPrimary; 30 | max-height: 600px; 31 | transition: all 0.2s ease; 32 | border-radius: 4px; 33 | box-shadow: var(--elevation-stroke, 0 0 0 1px #020203), var(--elevation-high, 0 8px 16px #000); 34 | 35 | div.stickers { 36 | height: 550px !important; 37 | margin-bottom: 100px; 38 | position: relative; 39 | 40 | &.has-left-toolbar { 41 | height: 600px !important; 42 | margin-left: 50px; 43 | } 44 | 45 | h3.getStarted { 46 | text-align: center; 47 | padding-top: 40%; 48 | pointer-events: none; 49 | } 50 | 51 | div.pack { 52 | float: left; 53 | display: flex; 54 | flex-flow: wrap; 55 | justify-content: center; 56 | padding: 20px; 57 | width: 100%; 58 | box-sizing: border-box; 59 | 60 | span { 61 | color: $textColor; 62 | width: 100%; 63 | cursor: auto; 64 | padding-left: 10px; 65 | margin: 10px 0px; 66 | 67 | .counts { 68 | padding-left: 0; 69 | } 70 | 71 | .counts span { 72 | padding: 0 0.5em; 73 | } 74 | } 75 | 76 | div.sticker { 77 | display: flex; 78 | align-items: center; 79 | justify-content: center; 80 | width: 100px; 81 | height: 100px; 82 | float: left; 83 | position: relative; 84 | 85 | .image { 86 | cursor: pointer; 87 | max-height: 100%; 88 | max-width: 100%; 89 | } 90 | 91 | div.addFavorite, div.deleteFavorite { 92 | width: 20px; 93 | height: 20px; 94 | position: absolute; 95 | right: 0; 96 | transition: all 0.2s ease; 97 | display: none; 98 | &:hover { 99 | transform: scale(1.25); 100 | svg path { 101 | transition: all 0.2s ease; 102 | } 103 | } 104 | } 105 | 106 | div.addFavorite { 107 | bottom: 0; 108 | &:hover { 109 | svg path { 110 | fill: #2ECC71; 111 | } 112 | } 113 | } 114 | 115 | div.deleteFavorite { 116 | top: 0px; 117 | transform: rotateZ(45deg); 118 | &:hover { 119 | transform: scale(1.25) rotateZ(45deg); 120 | svg path { 121 | fill: #F04747; 122 | } 123 | } 124 | } 125 | 126 | &:hover div.addFavorite, 127 | &:hover div.deleteFavorite { 128 | display: block; 129 | cursor: pointer; 130 | } 131 | } 132 | } 133 | } 134 | 135 | div.packs-toolbar { 136 | position: absolute; 137 | bottom: 0; 138 | background: $backgroundSecondary; 139 | display: flex; 140 | 141 | &.has-scroll-x { 142 | width: 100%; 143 | height: 50px; 144 | 145 | div.packs { 146 | flex: 1 0 auto; 147 | 148 | &.packs-controls { 149 | flex: 0 0 auto; 150 | } 151 | 152 | div.packs-wrapper { 153 | white-space: nowrap; 154 | float: left; 155 | width: 100%; 156 | font-size: 0; /* quick hax to clear whitespace */ 157 | } 158 | } 159 | } 160 | 161 | &.has-scroll-y { 162 | width: 50px; 163 | height: 100%; 164 | flex-direction: column; 165 | 166 | div.packs { 167 | flex: 1 1 auto; 168 | height: 100%; 169 | 170 | &.packs-controls { 171 | flex: 0 0 auto; 172 | height: auto; 173 | } 174 | 175 | div.packs-wrapper { 176 | font-size: 0; /* quick hax to clear whitespace */ 177 | } 178 | } 179 | } 180 | 181 | div.packs div.pack { 182 | display: inline-block; 183 | height: 40px; 184 | width: 40px; 185 | margin: 5px; 186 | cursor: pointer; 187 | background-position: center; 188 | background-size: contain; 189 | background-repeat: no-repeat; 190 | transition: all 0.2s ease; 191 | filter: grayscale(100%); 192 | 193 | &:hover, 194 | div.pack.active { 195 | transform: scale(1.25); 196 | filter: grayscale(0%); 197 | } 198 | 199 | > div { 200 | background-image: url('/assets/62ed7720accb1adfe95565b114e843c6.png'); 201 | width: 32px; 202 | height: 32px; 203 | background-size: 1344px 1216px; 204 | background-repeat: no-repeat; 205 | margin-top: 4px; 206 | margin-left: 4px; 207 | } 208 | 209 | div.icon-favorite { 210 | background-position: -1056px -288px; 211 | } 212 | 213 | div.icon-plus { 214 | background-position: -384px -896px; 215 | /* make it greenish */ 216 | /* thanks to the magic of https://codepen.io/sosuke/pen/Pjoqqp */ 217 | filter: invert(63%) sepia(25%) saturate(813%) hue-rotate(55deg) brightness(98%) contrast(82%); 218 | } 219 | 220 | div.icon-frequently-used { 221 | background-position: -160px -960px; 222 | } 223 | } 224 | } 225 | } 226 | 227 | .stickersModal { 228 | bottom: 0; 229 | left: 0; 230 | position: absolute; 231 | right: 0; 232 | top: 0; 233 | align-items: center; 234 | justify-content: center; 235 | 236 | &.is-active { 237 | display: flex; 238 | } 239 | 240 | .inputQuery { 241 | width: calc(100% - 30px); 242 | height: 36px; 243 | box-sizing: border-box; 244 | margin: 0 15px 10px; 245 | padding: 5px 12px; 246 | border-radius: 3px; 247 | border: 1px solid $backgroundPrimary; 248 | background: $backgroundPrimary; 249 | color: $textColor; 250 | } 251 | 252 | textarea.inputQuery { 253 | height: auto; 254 | min-height: 54px; 255 | } 256 | 257 | .inputPackIndex { 258 | width: 55px; 259 | height: 36px; 260 | box-sizing: border-box; 261 | padding: 5px 12px; 262 | border-radius: 3px; 263 | border: 1px solid $backgroundPrimary; 264 | background: $backgroundPrimary; 265 | color: $textColor; 266 | text-align: center; 267 | } 268 | 269 | .modal-background { 270 | bottom: 0; 271 | left: 0; 272 | position: absolute; 273 | right: 0; 274 | top: 0; 275 | width: 100%; 276 | height: 100%; 277 | background-color: rgba(10, 10, 10, 0.86); 278 | } 279 | 280 | .modal-content, 281 | .modal-card { 282 | position: absolute; 283 | width: 100%; 284 | height: 100%; 285 | left: 0; 286 | top: 0; 287 | background: $backgroundSecondary; 288 | } 289 | 290 | .modal-content { 291 | .stickersConfig { 292 | height: 100%; 293 | width: 100%; 294 | display: flex; 295 | flex-direction: column; 296 | 297 | .tabs { 298 | width: 100%; 299 | text-align: center; 300 | 301 | .tab { 302 | color: $textColor; 303 | display: inline-block; 304 | border: none; 305 | border-top: 0px transparent; 306 | border-left: 0px transparent; 307 | border-right: 0px transparent; 308 | border-width: 1px; 309 | border-style: solid; 310 | border-bottom-color: $textColor; 311 | padding: 20px; 312 | cursor: pointer; 313 | 314 | &:hover, 315 | &.is-active { 316 | border-bottom-color: $hoverColor; 317 | color: $hoverColor; 318 | } 319 | } 320 | } 321 | 322 | div.tab-content { 323 | height: calc(100% - 66px); /* .tabs height */ 324 | width: 100%; 325 | padding: 10px 0; 326 | box-sizing: border-box; 327 | 328 | &.avail-packs { 329 | display: flex; 330 | flex-direction: column; 331 | padding-bottom: 0; 332 | 333 | .packs { 334 | height: 100%; 335 | width: 100%; 336 | padding-bottom: 10px; 337 | } 338 | } 339 | 340 | &.import, 341 | &.misc { 342 | user-select: text; 343 | 344 | .section { 345 | padding: 0 24px 14px; 346 | 347 | .section-title { 348 | font-weight: 800; 349 | } 350 | 351 | > p:last-of-type { 352 | margin-bottom: 0; 353 | } 354 | 355 | a { 356 | color: var(--text-link, #5197ed); 357 | 358 | &:hover { 359 | text-decoration: underline; 360 | } 361 | } 362 | } 363 | 364 | .input-grouped { 365 | display: flex; 366 | 367 | input { 368 | margin: 0; 369 | width: auto; 370 | flex-grow: 1; 371 | } 372 | 373 | textarea { 374 | margin: 0; 375 | width: auto; 376 | flex-grow: 1; 377 | } 378 | 379 | button { 380 | margin-left: 4px; 381 | } 382 | } 383 | } 384 | } 385 | 386 | div.pack { 387 | height: 75px; 388 | width: 100%; 389 | float: left; 390 | display: flex; 391 | padding: 0 20px; 392 | box-sizing: border-box; 393 | margin-bottom: 5px; 394 | 395 | &:last-of-type { 396 | margin-bottom: 0; 397 | } 398 | 399 | div.index, 400 | div.handle, 401 | div.preview { 402 | flex: 0 0 auto; 403 | min-width: 75px; 404 | } 405 | 406 | div.action { 407 | flex: 1 0 auto; 408 | 409 | &.is-tight button { 410 | width: auto; 411 | padding-right: 0.5em; 412 | padding-left: 0.5em; 413 | } 414 | 415 | button.delete-pack { 416 | width: 36px; 417 | height: 36px; 418 | 419 | &:before, 420 | &:after { 421 | background-color: $textColor; 422 | content: ""; 423 | display: block; 424 | left: 50%; 425 | position: absolute; 426 | top: 50%; 427 | transform: translateX(-50%) translateY(-50%) rotate(45deg); 428 | transform-origin: center center; 429 | } 430 | 431 | &:before { 432 | height: 2px; 433 | width: 50%; 434 | } 435 | 436 | &:after { 437 | height: 50%; 438 | width: 2px; 439 | } 440 | } 441 | } 442 | 443 | div.index { 444 | padding-top: 20px; 445 | text-align: left; 446 | } 447 | 448 | div.preview { 449 | height: 75px; 450 | background-position: center; 451 | background-size: contain; 452 | background-repeat: no-repeat; 453 | } 454 | 455 | div.handle { 456 | padding: 20px; 457 | cursor: move; 458 | padding-top: 30px; 459 | 460 | span { 461 | background: #555; 462 | height: 2px; 463 | width: 100%; 464 | display: block; 465 | margin-bottom: 6px; 466 | } 467 | } 468 | 469 | div.action { 470 | padding-top: 20px; 471 | text-align: right; 472 | } 473 | 474 | div.info { 475 | flex: 1 1 auto; 476 | padding: 14px; 477 | 478 | > span { 479 | display: block; 480 | width: 100%; 481 | color: $textColor; 482 | 483 | &:nth-of-type(1) { 484 | font-weight: bold; 485 | color: $textColor; 486 | } 487 | 488 | .appendix span { 489 | &:nth-of-type(1) { 490 | padding: 0 0.5em; 491 | } 492 | 493 | &:nth-of-type(2) { 494 | user-select: text; 495 | } 496 | } 497 | } 498 | } 499 | 500 | div.preview img { 501 | height: 100%; 502 | width: 100%; 503 | } 504 | } 505 | } 506 | } 507 | 508 | .modal-close { 509 | user-select: none; 510 | background-color: rgba(10, 10, 10, 0.2); 511 | border: none; 512 | border-radius: 290486px; 513 | cursor: pointer; 514 | display: inline-block; 515 | flex-grow: 0; 516 | flex-shrink: 0; 517 | font-size: 0; 518 | outline: none; 519 | vertical-align: top; 520 | background: none; 521 | position: absolute; 522 | right: 20px; 523 | top: 20px; 524 | height: 32px; 525 | max-height: 32px; 526 | max-width: 32px; 527 | min-height: 32px; 528 | min-width: 32px; 529 | width: 32px; 530 | 531 | &:before, 532 | &:after { 533 | background-color: $textColor; 534 | content: ""; 535 | display: block; 536 | left: 50%; 537 | position: absolute; 538 | top: 50%; 539 | transform: translateX(-50%) translateY(-50%) rotate(45deg); 540 | transform-origin: center center; 541 | } 542 | 543 | &:before { 544 | height: 2px; 545 | width: 50%; 546 | } 547 | 548 | &:after { 549 | height: 50%; 550 | width: 2px; 551 | } 552 | 553 | &:hover, 554 | &:focus { 555 | background-color: rgba(10, 10, 10, 0.3); 556 | } 557 | } 558 | } 559 | 560 | .button { 561 | align-items: center; 562 | border: 1px solid transparent; 563 | border-radius: 3px; 564 | box-shadow: none; 565 | display: inline-flex; 566 | font-size: 1rem; 567 | padding: calc(0.375em - 1px) 0.75em; 568 | position: relative; 569 | vertical-align: top; 570 | user-select: none; 571 | cursor: pointer; 572 | justify-content: center; 573 | text-align: center; 574 | white-space: nowrap; 575 | border-color: transparent; 576 | color: $textColor; 577 | background-color: $backgroundPrimary; 578 | width: 62px; /* consistent width */ 579 | 580 | &.is-danger { 581 | color: #ffffff; 582 | border-color: rgba(240,71,71,.3); 583 | background: #f04747; 584 | } 585 | 586 | &:hover, &.is-primary:hover { 587 | transform: scale3d(1.1, 1.1, 1.1); 588 | } 589 | 590 | &.has-width-full { 591 | width: 100%; 592 | 593 | &:hover { 594 | /* TODO: Figure out how to do a more consistent scaling, 595 | regardless of the button's dynamic size. */ 596 | transform: scale3d(1.04, 1.04, 1.04); 597 | } 598 | } 599 | } 600 | 601 | .has-scroll-x { 602 | overflow-x: auto; 603 | scrollbar-gutter: stable; 604 | } 605 | 606 | .has-scroll-y { 607 | overflow-y: auto; 608 | scrollbar-gutter: stable; 609 | } 610 | 611 | ::-webkit-scrollbar { 612 | /* Let's make the scrollbars pretty */ 613 | width: 6px; 614 | height: 6px; 615 | 616 | &-track { 617 | margin: 0; 618 | background: transparent; 619 | border-radius: 5px; 620 | 621 | &-piece { 622 | border: 0 solid transparent; 623 | background: transparent; 624 | margin: 0; 625 | } 626 | } 627 | 628 | &-thumb { 629 | background: rgba($scrollbarColor, 0.5); 630 | border: 0 solid transparent; 631 | border-radius: 5px; 632 | 633 | &:hover { 634 | background: rgba($scrollbarColor, 0.75); 635 | } 636 | 637 | &:active { 638 | background: rgba($scrollbarColor, 1); 639 | } 640 | } 641 | } 642 | 643 | code { 644 | box-sizing: border-box; 645 | padding: 2px 6px; 646 | border-radius: 3px; 647 | border: 1px solid $backgroundPrimary; 648 | background: $backgroundPrimary; 649 | color: $textColor; 650 | } 651 | } 652 | 653 | div#maganeButton { 654 | &.channel-textarea-stickers { 655 | display: flex; 656 | align-items: center; 657 | cursor: pointer; 658 | 659 | &:hover, &.active { 660 | filter: brightness(1.35); 661 | } 662 | } 663 | 664 | img.channel-textarea-stickers-content { 665 | width: 24px; 666 | height: 24px; 667 | padding: 4px; 668 | margin-left: 2px; 669 | margin-right: 2px; 670 | } 671 | } 672 | 673 | /* Visually hide Magane button in certain scenarios */ 674 | 675 | div[class^="submitContainer_"] div#maganeButton { /* create thread */ 676 | display: none; 677 | } 678 | 679 | div[data-list-item-id^="forum-channel-list-"] div#maganeButton { /* create forum post */ 680 | display: none; 681 | } 682 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 2333 | 2334 |
2335 |
2337 | 2338 |
2339 |
2340 | { #if !favoriteStickers.length && !subscribedPacks.length } 2341 |

It seems you aren't subscribed to any pack yet. Click the plus symbol on the bottom-left to get started! 🎉

2342 | { /if } 2343 | { #if favoriteStickers.length } 2344 |
2345 | Favorites{ @html formatStickersCount(favoriteStickers.length) } 2346 | { #each favoriteStickers as sticker, i } 2347 |
2348 | { sticker.pack } - { sticker.id } 2355 |
2358 | 2359 | 2360 | 2361 |
2362 |
2363 | { /each } 2364 |
2365 | { /if } 2366 | 2367 | { #if frequentlyUsedSorted.length } 2368 |
2369 | Frequently Used{ @html formatStickersCount(frequentlyUsedSorted.length) } 2370 | { #each frequentlyUsedSorted as sticker, i } 2371 |
2372 | { sticker.pack } - { sticker.id } 2379 | { #if favoriteStickers.findIndex(f => f.pack === sticker.pack && f.id === sticker.id) === -1 } 2380 |
2383 | 2384 | 2385 | 2386 |
2387 | { :else } 2388 |
2391 | 2392 | 2393 | 2394 |
2395 | { /if } 2396 |
2397 | { /each } 2398 |
2399 | { /if } 2400 | 2401 | { #each subscribedPacks as pack, i } 2402 |
2403 | { pack.name }{ @html formatStickersCount(pack.files.length) } 2404 | 2405 | { #each pack.files as sticker, i } 2406 |
2407 | { pack.id } - { sticker } 2413 | { #if favoriteStickers.findIndex(f => f.pack === pack.id && f.id === sticker) === -1 } 2414 |
2417 | 2418 | 2419 | 2420 |
2421 | { :else } 2422 |
2425 | 2426 | 2427 | 2428 |
2429 | { /if } 2430 |
2431 | { /each } 2432 |
2433 | { /each } 2434 |
2435 | 2436 |
2437 |
2438 |
2439 |
2442 |
2443 |
2444 | { #if favoriteSticker.length } 2445 |
scrollToStickers('#pfavorites') } 2447 | title="Favorites" > 2448 |
2449 |
2450 | { /if } 2451 | { #if frequentlyUsedSorted.length } 2452 |
scrollToStickers('#pfrequentlyused') } 2454 | title="Frequently Used" > 2455 |
2456 |
2457 | { /if } 2458 |
2459 |
2460 | 2461 |
2462 |
2463 | { #each subscribedPacks as pack, i } 2464 |
scrollToStickers(`#p${pack.id}`) } 2466 | title="{ pack.name }" 2467 | style="background-image: { `url(${formatUrl(pack.id, pack.files[0], false, 0)})` }" /> 2468 | { /each } 2469 |
2470 |
2471 |
2472 | 2473 |
2474 |