├── .editorconfig
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .hooks
└── pre-commit
├── .scannerwork
├── .sonar_lock
└── report-task.txt
├── AliuEval
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── aliueval
│ └── AliuEval.kt
├── AnimateApngs
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── animateapngs
│ └── AnimateApngs.java
├── BetterSpotify
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── betterspotify
│ ├── BetterSpotify.kt
│ ├── SpotifyApi.kt
│ └── models
│ └── PlayerInfo.kt
├── Brainfuck
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── brainfuck
│ └── Brainfuck.java
├── CheckLinks
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── checklinks
│ ├── CheckLinks.kt
│ └── Models.kt
├── DedicatedPluginSettings
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── dps
│ ├── DedicatedPluginSettings.java
│ ├── DragAndDropHelper.kt
│ └── PluginsAdapter.kt
├── EmojiReplacer
├── README.md
├── build.gradle.kts
├── makezip.js
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── emojireplacer
│ ├── EmojiReplacer.kt
│ ├── Settings.kt
│ └── Util.kt
├── EmojiUtility
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── emojiutility
│ ├── Commands.kt
│ ├── EmojiDownloader.java
│ ├── EmojiUtility.java
│ ├── Patches.java
│ ├── Settings.java
│ └── clonemodal
│ ├── Adapter.java
│ ├── Modal.java
│ └── ViewHolder.java
├── FixEmotes
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── fixemotes
│ └── FixEmotes.kt
├── GatewayLog
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── gatewaylog
│ └── GatewayLog.kt
├── Hastebin
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── hastebin
│ ├── HasteResponse.java
│ ├── Hastebin.java
│ └── PluginSettings.java
├── JsEval
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── jseval
│ ├── CodeTextView.kt
│ ├── JsEval.kt
│ ├── TerminalButton.kt
│ ├── TerminalHistoryAdapter.kt
│ └── TerminalPage.kt
├── LICENSE
├── MessageLinkEmbeds
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── messagelinkembeds
│ └── MessageLinkEmbeds.kt
├── PlayableEmbeds
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── betterplatformembeds
│ └── PlayableEmbeds.kt
├── PluginDownloader
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── plugindownloader
│ ├── Modal.java
│ ├── PDUtil.java
│ ├── Plugin.java
│ └── PluginDownloader.java
├── README.md
├── ShowBlockedMessages
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── showblockedmessages
│ └── ShowBlockedMessages.kt
├── TapTap
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── taptap
│ ├── TapTap.java
│ └── TapTapSettings.kt
├── Template
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── template
│ └── Template.kt
├── TextFilePreview
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugins
│ └── textfilepreview
│ ├── AttachmentPreviewWidget.kt
│ ├── LeSettings.kt
│ └── TextFilePreview.kt
├── Themer
├── Firefly.json
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── themer
│ ├── Constants.kt
│ ├── Legacy.kt
│ ├── Patches.kt
│ ├── ResourceManager.kt
│ ├── Theme.kt
│ ├── ThemeLoader.kt
│ ├── Themer.kt
│ ├── Util.kt
│ └── settings
│ ├── ThemeCard.kt
│ ├── ThemeLayout.kt
│ ├── ThemerSettings.kt
│ └── editor
│ ├── ThemeEditor.kt
│ └── tabs
│ ├── FormInputTabs.kt
│ ├── Tab.kt
│ └── color
│ ├── ColorAdapter.kt
│ ├── ColorPickerListener.kt
│ ├── ColorTab.kt
│ ├── ColorTuple.kt
│ ├── ColorViewHolder.kt
│ └── NewColorDialog.kt
├── UrbanDictionary
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── urbandictionary
│ ├── ApiResponse.kt
│ └── UrbanDictionary.kt
├── ViewProfileImages
├── README.md
├── build.gradle.kts
├── demo.gif
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── dev
│ └── vendicated
│ └── aliucordplugs
│ └── viewprofileimages
│ └── ViewProfileImages.java
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── licenseHeader.txt
├── licenser.sh
└── new.sh
├── settings.gradle.kts
└── uwu
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | max_line_length = 150
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | concurrency:
4 | group: "build"
5 | cancel-in-progress: true
6 |
7 |
8 | on:
9 | push:
10 | branches:
11 | - main
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-20.04
16 | permissions:
17 | contents: write
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@master
21 | with:
22 | path: "src"
23 |
24 | - name: Checkout builds
25 | uses: actions/checkout@master
26 | with:
27 | ref: "builds"
28 | path: "builds"
29 |
30 | - name: Setup JDK 11
31 | uses: actions/setup-java@v2
32 | with:
33 | java-version: 11
34 | distribution: zulu
35 | cache: gradle
36 |
37 | - name: Setup Android SDK
38 | uses: android-actions/setup-android@v2
39 |
40 | - name: Build Plugins
41 | run: |
42 | cd $GITHUB_WORKSPACE/src
43 | chmod +x gradlew
44 | ./gradlew make generateUpdaterJson
45 | cp **/build/*.zip $GITHUB_WORKSPACE/builds
46 | cp build/updater.json $GITHUB_WORKSPACE/builds
47 |
48 | - name: Push builds
49 | run: |
50 | cd $GITHUB_WORKSPACE/builds
51 | git config --local user.email "actions@github.com"
52 | git config --local user.name "GitHub Actions"
53 | git add .
54 | git commit -m "Build $GITHUB_SHA" || true # do not error if nothing to commit
55 | git push
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | **/build
12 | Test
13 | OwO
14 | sonar-project.properties
15 | # unfinished
16 | SnowflakeLookup
17 |
--------------------------------------------------------------------------------
/.hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -ex
3 |
4 | scripts/licenser.sh
5 |
--------------------------------------------------------------------------------
/.scannerwork/.sonar_lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vendicated/AliucordPlugins/a1decb53c13219145ef2336c818d5e2b348f61cc/.scannerwork/.sonar_lock
--------------------------------------------------------------------------------
/.scannerwork/report-task.txt:
--------------------------------------------------------------------------------
1 | projectKey=AliucordPlugins
2 | serverUrl=http://localhost:9000
3 | serverVersion=9.1.0.47736
4 | dashboardUrl=http://localhost:9000/dashboard?id=AliucordPlugins
5 | ceTaskId=AX2Io6lgKu2Z6sTfyIcy
6 | ceTaskUrl=http://localhost:9000/api/ce/task?id=AX2Io6lgKu2Z6sTfyIcy
7 |
--------------------------------------------------------------------------------
/AliuEval/README.md:
--------------------------------------------------------------------------------
1 | # Template
2 |
3 | This is the Template I generate all my plugins from. Not much to see here :)
4 |
--------------------------------------------------------------------------------
/AliuEval/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.0"
2 | description = "Punch 6pak for making me update 15 plugins"
3 |
--------------------------------------------------------------------------------
/AliuEval/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AliuEval/src/main/kotlin/dev/vendicated/aliucordplugins/aliueval/AliuEval.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.aliueval
12 |
13 | import android.content.Context
14 | import com.aliucord.Http
15 | import com.aliucord.annotations.AliucordPlugin
16 | import com.aliucord.api.CommandsAPI
17 | import com.aliucord.entities.Plugin
18 | import com.aliucord.utils.*
19 | import dalvik.system.DexClassLoader
20 | import java.io.File
21 | import java.util.*
22 |
23 | class Code(val code: String)
24 | class CompileError(val stdout: String, val stderr: String)
25 |
26 | @AliucordPlugin
27 | class AliuEval : Plugin() {
28 | override fun start(ctx: Context) {
29 | commands.registerCommand("eval", "evaluate kotlin", CommandsAPI.requiredMessageOption) {
30 | try {
31 | val code = it.getRequiredString("message")
32 | val outFile = File(ctx.codeCacheDir, "eval.dex")
33 | Http.Request("https://aliueval.vendicated.dev", "POST").use {
34 | it.executeWithJson(Code(code)).saveToFile(outFile)
35 | }
36 | val cl = DexClassLoader(outFile.absolutePath, ctx.codeCacheDir.absolutePath, null, this.javaClass.classLoader)
37 | val clazz = cl.loadClass("dev.vendicated.aliucordeval.Eval")
38 | val instance = ReflectUtils.invokeConstructorWithArgs(clazz, patcher, settings, commands)
39 | val ret = ReflectUtils.invokeMethod(instance, "main")
40 | try {
41 | CommandsAPI.CommandResult("```json\n${GsonUtils.toJson(ret)}```", null, false)
42 | } catch (th: Throwable) {
43 | CommandsAPI.CommandResult("```${Objects.toString(ret)}```", null, false)
44 | }
45 | } catch (th: Throwable) {
46 | val msg = if (th is Http.HttpException && th.statusCode == 400) {
47 | val err = GsonUtils.fromJson(th.req.conn.errorStream.use { IOUtils.readAsText(it) }, CompileError::class.java)
48 | "STDOUT: ```\n${err.stdout + " "}```\nSTDERR: ```\n${err.stderr}```"
49 | } else "```\n${th.message}```"
50 | CommandsAPI.CommandResult(msg, null, false)
51 | }
52 | }
53 | }
54 |
55 | override fun stop(context: Context) {
56 | patcher.unpatchAll()
57 | commands.unregisterAll()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/AnimateApngs/README.md:
--------------------------------------------------------------------------------
1 | # AnimateApngs - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/AnimateApngs.zip?raw=true)
2 |
3 | Simple plugin that makes apngs animate just like gifs
4 |
5 | ## Limitations
6 |
7 | Only images hosted on cdn.discordapp.com or media.discordapp.net links will be animated. Discord's proxy server does not serve apngs,
8 | so to animate proxied images the proxy would have to be stripped which would expose your IP to the image host. Thus third party image links can not be animated.
9 |
--------------------------------------------------------------------------------
/AnimateApngs/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.4"
2 | description = "Makes apngs embeds animate properly"
3 |
--------------------------------------------------------------------------------
/AnimateApngs/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AnimateApngs/src/main/java/dev/vendicated/aliucordplugs/animateapngs/AnimateApngs.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.animateapngs;
12 |
13 | import android.content.Context;
14 | import android.net.Uri;
15 | import android.widget.ImageView;
16 |
17 | import com.aliucord.Http;
18 | import com.aliucord.Utils;
19 | import com.aliucord.annotations.AliucordPlugin;
20 | import com.aliucord.entities.Plugin;
21 | import com.aliucord.patcher.Hook;
22 | import com.discord.api.message.embed.EmbedType;
23 | import com.discord.embed.RenderableEmbedMedia;
24 | import com.discord.widgets.chat.list.InlineMediaView;
25 | import com.discord.widgets.media.WidgetMedia;
26 |
27 | import java.util.regex.Pattern;
28 |
29 | @AliucordPlugin
30 | public class AnimateApngs extends Plugin {
31 | private void initApng(ImageView view, String mediaUrl, Integer w, Integer h) {
32 | final var url = mediaUrl
33 | // Strip proxy but only if they're Discord domains.
34 | .replaceFirst("https://images-ext-.*?\\.discordapp\\.net/external/.*?/https/(?:media|cdn)\\.discordapp\\.(?:net|com)", "https://cdn.discordapp.com")
35 | // Media server serves them as regular PNGs, only CDN serves actual APNGs.
36 | .replace("media.discordapp.net", "cdn.discordapp.com");
37 |
38 | Utils.threadPool.execute(() -> {
39 | try (var is = new Http.Request(url).execute().stream()) {
40 | // You cannot consume the stream twice so just yolo it and don't use Apng.Companion.isApng first, eh whatever.
41 | var drawable = b.l.a.a.a(is, w, h);
42 | if (view != null)
43 | Utils.mainThread.post(() -> {
44 | view.setImageDrawable(drawable);
45 | drawable.start();
46 | });
47 | } catch (Throwable ignored) { }
48 | });
49 | }
50 |
51 | @Override
52 | public void start(Context ctx) throws Throwable {
53 | int previewResId = Utils.getResId("inline_media_image_preview", "id");
54 | int mediaResId = Utils.getResId("media_image", "id");
55 |
56 | var updateUI = InlineMediaView.class.getDeclaredMethod("updateUI", RenderableEmbedMedia.class, String.class, EmbedType.class, Integer.class, Integer.class, String.class);
57 | patcher.patch(updateUI, new Hook(param -> {
58 | var media = (RenderableEmbedMedia) param.args[0];
59 | if (media == null || !media.a.endsWith(".png")) return;
60 |
61 | // Media server serves them as regular PNGs, only CDN serves actual APNGs.
62 | var url = media.a.replace("media.discordapp.net", "cdn.discordapp.com");
63 | var binding = InlineMediaView.access$getBinding$p((InlineMediaView) param.thisObject);
64 | var view = (ImageView) binding.getRoot().findViewById(previewResId);
65 | initApng(view, url, media.b, media.c);
66 | }));
67 |
68 |
69 | var pattern = Pattern.compile("\\.png(?:\\?width=(\\d+)&height=(\\d+))?");
70 |
71 | var uriField = WidgetMedia.class.getDeclaredField("imageUri");
72 | uriField.setAccessible(true);
73 | var getFormattedUrl = WidgetMedia.class.getDeclaredMethod("getFormattedUrl", Context.class, Uri.class);
74 | getFormattedUrl.setAccessible(true);
75 |
76 | patcher.patch(WidgetMedia.class.getDeclaredMethod("configureMediaImage"), new Hook(param -> {
77 | try {
78 | var widgetMedia = (WidgetMedia) param.thisObject;
79 | var url = (String) getFormattedUrl.invoke(widgetMedia, widgetMedia.requireContext(), uriField.get(widgetMedia));
80 | if (url == null) return;
81 | var match = pattern.matcher(url);
82 | if (match.find()) {
83 | String w = match.group(1);
84 | String h = match.group(2);
85 | var view = (ImageView) WidgetMedia.access$getBinding$p(widgetMedia).getRoot().findViewById(mediaResId);
86 | initApng(view, url, w != null ? Integer.parseInt(w) : null, h != null ? Integer.parseInt(h) : null);
87 | }
88 | } catch (Throwable ignored) { }
89 | }));
90 | }
91 |
92 | @Override
93 | public void stop(Context context) {
94 | patcher.unpatchAll();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/BetterSpotify/README.md:
--------------------------------------------------------------------------------
1 | # BetterSpotify - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/BetterSpotify.zip?raw=true)
2 |
3 | Better Spotify integration.
4 |
5 | Features:
6 | - Listen Along - You MUST have spotify running for listen along to work! so play a song first then use it
7 | - Commands to send current song or album in chat (sendSpotifySong/sendSpotifyAlbum)
8 |
9 | 
10 |
11 | 
12 |
--------------------------------------------------------------------------------
/BetterSpotify/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.1.5"
2 | description = "Better Spotify Integration - Listen along with your friends!"
3 |
4 | aliucord.changelog.set("""
5 | # 1.1.4
6 | * Remove spotify embeds. They are now part of my new plugin PlayableEmbeds, which also adds playable youtube embeds.
7 |
8 | # 1.1.2
9 | * Make spotify embeds scrollable <:blobcatcozy:859801776232202280>
10 |
11 | # 1.1.0
12 | * add sendSpotifySong and sendSpotifyAlbum commands
13 | * add rich spotify embeds with play button like on desktop
14 | """.trimIndent())
15 |
--------------------------------------------------------------------------------
/BetterSpotify/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/BetterSpotify/src/main/kotlin/dev/vendicated/aliucordplugins/betterspotify/SpotifyApi.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.betterspotify
12 |
13 | import com.aliucord.Http
14 | import com.aliucord.Utils
15 | import com.aliucord.utils.ReflectUtils
16 | import com.aliucord.utils.RxUtils
17 | import com.aliucord.utils.RxUtils.await
18 | import com.aliucord.utils.RxUtils.createActionSubscriber
19 | import com.aliucord.utils.RxUtils.subscribe
20 | import com.discord.stores.StoreStream
21 | import com.discord.utilities.platform.Platform
22 | import com.discord.utilities.rest.RestAPI
23 | import com.discord.utilities.spotify.SpotifyApiClient
24 | import dev.vendicated.aliucordplugins.betterspotify.models.PlayerInfo
25 | import java.util.concurrent.TimeUnit
26 | import kotlin.math.abs
27 |
28 | private const val baseUrl = "https://api.spotify.com/v1/me/player"
29 |
30 | // The spotify api gives me fucking brain damage i swear to god
31 | // You can either specify album or playlist uris as "context_uri" String or track uris as "uris" array
32 | @Suppress("Unused")
33 | class SongBody(val uris: List, val position_ms: Int = 0)
34 |
35 | object SpotifyApi {
36 | val client: SpotifyApiClient by lazy {
37 | ReflectUtils.getField(StoreStream.getSpotify(), "spotifyApiClient") as SpotifyApiClient
38 | }
39 |
40 | private var token: String? = null
41 | private fun getToken(): String? {
42 | if (token == null) {
43 | token = RestAPI.AppHeadersProvider.INSTANCE.spotifyToken
44 | ?: try {
45 | val accountId = ReflectUtils.getField(client, "spotifyAccountId")
46 | val (res, err) = RestAPI.api
47 | .getConnectionAccessToken(Platform.SPOTIFY.name.lowercase(), accountId as String)
48 | .await()
49 | err?.let { throw it }
50 | res!!.accessToken
51 | } catch (th: Throwable) {
52 | null
53 | }
54 | }
55 | return token
56 | }
57 |
58 | private var didTokenRefresh = false
59 | private fun request(endpoint: String, method: String = "PUT", data: Any? = null, cb: ((Http.Response) -> Unit)? = null) {
60 | Utils.threadPool.execute {
61 | val token = getToken() ?: run {
62 | Utils.showToast("Failed to get Spotify token from Discord. Make sure your spotify is running.")
63 | return@execute
64 | }
65 |
66 | try {
67 | Http.Request("$baseUrl/$endpoint", method)
68 | .setHeader("Authorization", "Bearer $token")
69 | .use {
70 | val res =
71 | if (data != null)
72 | it.executeWithJson(data)
73 | else
74 | it
75 | .setHeader("Content-Type", "application/json")
76 | .execute()
77 |
78 | res.assertOk()
79 | cb?.invoke(res)
80 | }
81 | } catch (th: Throwable) {
82 | if (th is Http.HttpException) {
83 | when (th.statusCode) {
84 | 401 -> {
85 | if (!didTokenRefresh) {
86 | didTokenRefresh = true
87 | SpotifyApiClient.`access$refreshSpotifyToken`(client)
88 | this.token = null
89 | RxUtils.timer(5, TimeUnit.SECONDS).subscribe(
90 | createActionSubscriber({
91 | request(endpoint, method, data, cb)
92 | })
93 | )
94 | } else {
95 | BetterSpotify.stopListening(skipToast = true)
96 | logger.errorToast("Got \"Unauthorized\" Error. Try relinking Spotify")
97 | }
98 | return@execute
99 | }
100 | 404 -> {
101 | BetterSpotify.stopListening(skipToast = true)
102 | logger.errorToast("Failed to play. Make sure your Spotify is running", th)
103 | return@execute
104 | }
105 | }
106 | logger.errorToast("Failed to play that song :( Check the debug log", th)
107 | }
108 | }
109 | }
110 | }
111 |
112 | fun getPlayerInfo(cb: (PlayerInfo) -> Unit) {
113 | request("", "GET", cb = {
114 | cb.invoke(it.json(PlayerInfo::class.java))
115 | })
116 | }
117 |
118 | fun playSong(id: String, position_ms: Int) {
119 | request("play", "PUT", SongBody(listOf("spotify:track:$id"), position_ms))
120 | }
121 |
122 | fun pause() {
123 | request("pause", "PUT")
124 | }
125 |
126 | fun resume() {
127 | request("play", "PUT")
128 | }
129 |
130 | fun seek(position_ms: Int) {
131 | getPlayerInfo {
132 | if (!it.is_playing)
133 | playSong(it.item.id, position_ms)
134 | else if (abs(it.progress_ms - position_ms) > 5000)
135 | request("seek?position_ms=$position_ms")
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/BetterSpotify/src/main/kotlin/dev/vendicated/aliucordplugins/betterspotify/models/PlayerInfo.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.betterspotify.models
12 |
13 | class PlayerInfo(
14 | val progress_ms: Int,
15 | val is_playing: Boolean,
16 | val currently_playing_type: String,
17 | val shuffle_state: Boolean,
18 | val repeat_state: String,
19 | val device: Device,
20 | val item: Item
21 | ) {
22 | class Device(val name: String, val volume_percent: Int)
23 | class Item(val id: String, val name: String, val type: String, val uri: String)
24 | }
25 |
--------------------------------------------------------------------------------
/Brainfuck/README.md:
--------------------------------------------------------------------------------
1 | # Brainfuck - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/Brainfuck.zip?raw=true)
2 |
3 | Brainfuck is an extremely minimal esoteric programming language with only 8 instructions:
4 | - `>`
5 | - `<`
6 | - `+`
7 | - `-`
8 | - `.`
9 | - `,`
10 | - `[`
11 | - `]`
12 |
13 | [More info (Wikipedia)](https://en.wikipedia.org/wiki/Brainfuck)
14 |
15 | This plugin includes both a command to interpret brainfuck (`/brainfuck`) and a command to convert plain text to brainfuck (`/tobrainfuck`), because who doesn't want to send
16 | ```bf
17 | >+++++++++[<+++++++++>-]<++++++.>+++++[<+++++>-]<-.++++++++.>+++++++++[<--------->-]<++++++.>+++[<--->-]
18 | <---.>++++++[<++++++>-]<+++++.>++++++[<------>-]<-----.>+++++++++[<+++++++++>-]<++.++.---.>++++[<---->-]
19 | <+++.>++++++++[<-------->-]<-----.>++++++++[<++++++++>-]<+.>+++[<+++>-]<+++.>+++++++++[<--------->-]<+++
20 | +.>+++++++++[<+++++++++>-]<++++++.>+++++[<----->-]<+++.++.++++++++.>++++[<++++>-]<--.
21 | ```
22 | in the middle of a conversation!!!!
23 |
24 | What does that say? Install the plugin and find out ;)
25 |
26 | ### Why?
27 |
28 | 
29 |
--------------------------------------------------------------------------------
/Brainfuck/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.1"
2 | description = "Evaluates and converts to brainfuck"
3 |
--------------------------------------------------------------------------------
/Brainfuck/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/CheckLinks/README.md:
--------------------------------------------------------------------------------
1 | # CheckLinks - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/CheckLinks.zip?raw=true)
2 |
3 | Simple plugin that checks links using the VirusTotal api and shows the result in the "Are you sure you want to open this link" modal
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/CheckLinks/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.8"
2 | description = "Checks links via the VirusTotal api"
3 |
4 | aliucord.changelog.set("""
5 | Fix
6 | """.trimIndent())
7 |
--------------------------------------------------------------------------------
/CheckLinks/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/CheckLinks/src/main/java/dev/vendicated/aliucordplugs/checklinks/CheckLinks.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.checklinks
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.text.SpannableString
16 | import android.text.Spanned
17 | import android.text.method.LinkMovementMethod
18 | import android.text.style.ClickableSpan
19 | import android.text.style.URLSpan
20 | import android.view.View
21 | import android.widget.*
22 | import androidx.viewbinding.ViewBinding
23 | import com.aliucord.*
24 | import com.aliucord.Http.QueryBuilder
25 | import com.aliucord.annotations.AliucordPlugin
26 | import com.aliucord.entities.Plugin
27 | import com.aliucord.fragments.SettingsPage
28 | import com.aliucord.patcher.Hook
29 | import com.aliucord.utils.DimenUtils
30 | import com.discord.app.AppDialog
31 | import com.lytefast.flexinput.R
32 | import dev.vendicated.aliucordplugs.checklinks.*
33 | import java.lang.reflect.Method
34 | import java.util.*
35 |
36 | class MoreInfoModal(private val data: Map) : SettingsPage() {
37 | override fun onViewBound(view: View) {
38 | super.onViewBound(view)
39 | setActionBarTitle("URL info")
40 |
41 | val ctx = view.context
42 | val p = DimenUtils.defaultPadding
43 | val p2 = p / 2
44 |
45 | TableLayout(ctx).let { table ->
46 | for ((key, value) in data.toList().sortedBy { (_, value) -> value.result }.reversed()) {
47 | TableRow(ctx).let { row ->
48 | TextView(ctx, null, 0, R.i.UiKit_TextView).apply {
49 | text = key
50 | setPadding(p, p2, p, p2)
51 | row.addView(this)
52 | }
53 | TextView(ctx, null, 0, R.i.UiKit_TextView).apply {
54 | text = value.result
55 | setPadding(p, p2, p, p2)
56 | row.addView(this)
57 | }
58 | table.addView(row)
59 | }
60 | }
61 | addView(table)
62 | }
63 | }
64 | }
65 |
66 | private fun makeReq(url: String, method: String, contentType: String): Http.Request {
67 | val chars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
68 | val s = CharArray(10) { chars.random() }.joinToString("")
69 |
70 | return Http.Request(url, method).apply {
71 | setHeader("Content-Type", contentType)
72 | setHeader("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Firefox")
73 | setHeader("X-Tool", "vt-ui-main")
74 | setHeader("X-VT-Anti-Abuse-Header", s) // Can be anything for some reason
75 | setHeader("Accept-Ianguage", "en-US,en;q=0.9,es;q=0.8") // yes upper case i lol
76 | }
77 | }
78 |
79 | private fun checkLink(url: String): Map {
80 | // Look up url in cache first
81 | QueryBuilder("https://www.virustotal.com/ui/search").run {
82 | append("limit", "20")
83 | append("relationships[comment]", "author,item")
84 | append("query", url)
85 |
86 | makeReq(this.toString(), "GET", "application/json")
87 | .execute()
88 | .json(CachedUrlInfo::class.java)
89 | .let { res ->
90 | if (res.data.isNotEmpty()) return@checkLink res.data[0].attributes.last_analysis_results
91 | }
92 | }
93 |
94 | // no cached data, make full request for url
95 |
96 | // R.h.ster url to get an ID
97 | val idInfo =
98 | makeReq("https://www.virustotal.com/ui/urls", "POST", "application/x-www-form-urlencoded")
99 | .executeWithUrlEncodedForm(mapOf("url" to url))
100 | .json(UrlIdInfo::class.java)
101 |
102 | // Request analysis with that ID
103 | return makeReq(
104 | "https://www.virustotal.com/ui/analyses/" + idInfo.data.id,
105 | "GET",
106 | "application/json"
107 | )
108 | .execute()
109 | .json(NewUrlInfo::class.java)
110 | .data.attributes.results
111 | }
112 |
113 |
114 | @AliucordPlugin
115 | class CheckLinks : Plugin() {
116 | @SuppressLint("SetTextI18n")
117 | override fun start(ctx: Context) {
118 | var getBinding: Method? = null
119 |
120 | val dialogTextId = Utils.getResId("masked_links_body_text", "id")
121 |
122 | patcher.patch(
123 | b.a.a.g.a::class.java.getMethod("onViewBound", View::class.java),
124 | Hook { param ->
125 | val dialog = param.thisObject as AppDialog
126 | val url = dialog.arguments?.getString("WIDGET_SPOOPY_LINKS_DIALOG_URL")
127 | ?: return@Hook
128 |
129 | if (getBinding == null) {
130 | b.a.a.g.a::class.java.declaredMethods.find {
131 | ViewBinding::class.java.isAssignableFrom(it.returnType)
132 | }?.let {
133 | Logger("CheckLinks").info("Found obfuscated getBinding(): ${it.name}()")
134 | getBinding = it
135 | } ?: run {
136 | Logger("CheckLinks").error("Couldn't find obfuscated getBinding()", null)
137 | return@Hook
138 | }
139 | }
140 | val binding = getBinding!!.invoke(dialog) as ViewBinding
141 | val text = binding.root.findViewById(dialogTextId)
142 | text.text = "Checking URL $url..."
143 |
144 | Utils.threadPool.execute {
145 | var content: String
146 | var data: Map? = null
147 | try {
148 | data = checkLink(url)
149 |
150 | val counts = IntArray(4)
151 | data.values.forEach { v ->
152 | when (v.result) {
153 | "clean" -> counts[0]++
154 | "phishing" -> counts[1]++
155 | "malicious" -> counts[2]++
156 | else -> counts[3]++
157 | }
158 | }
159 |
160 | val malicious = counts[1] + counts[2]
161 | content =
162 | if (malicious > 0)
163 | "URL $url is ${if (malicious > 2) "likely" else "possibly"} malicious. $malicious engines flagged it as malicious."
164 | else
165 | "URL $url is either safe or too new to be flagged."
166 | } catch (th: Throwable) {
167 | Logger("[CheckLinks]").error("Failed to check link $url", th)
168 | content = "Failed to check URL $url. Proceed at your own risk."
169 | }
170 |
171 | if (data != null) content += "\n\nMore Info"
172 |
173 | SpannableString(content).run {
174 | val urlIdx = content.indexOf(url)
175 | setSpan(URLSpan(url), urlIdx, urlIdx + url.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
176 |
177 | data?.let {
178 | setSpan(object : ClickableSpan() {
179 | override fun onClick(view: View) {
180 | Utils.openPageWithProxy(view.context, MoreInfoModal(it))
181 | }
182 | }, content.length - 9, content.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
183 | }
184 |
185 | Utils.mainThread.post {
186 | text.movementMethod = LinkMovementMethod.getInstance()
187 | text.text = this
188 | }
189 | }
190 | }
191 | })
192 | }
193 |
194 | override fun stop(context: Context) {
195 | patcher.unpatchAll()
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/CheckLinks/src/main/java/dev/vendicated/aliucordplugs/checklinks/Models.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.checklinks
12 |
13 | class Entry(val result: String)
14 |
15 | class CachedUrlInfo(val data: List) {
16 | class Data(val attributes: Attributes) {
17 | class Attributes(val last_analysis_results: Map)
18 | }
19 | }
20 |
21 | class NewUrlInfo(val data: Data) {
22 | class Data(val attributes: Attributes) {
23 | class Attributes(val results: Map)
24 | }
25 | }
26 |
27 | class UrlIdInfo(val data: Data) {
28 | class Data(val id: String)
29 | }
30 |
--------------------------------------------------------------------------------
/DedicatedPluginSettings/README.md:
--------------------------------------------------------------------------------
1 | # DedicatedPluginSettings - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/DedicatedPluginSettings.zip?raw=true)
2 |
3 | Adds a dedicated plugin settings category in the settings page right below Aliucord's settings.
4 |
5 | 
6 |
7 |
8 | ### Submitting a plugin icon
9 |
10 | If you would like to submit an icon for your plugin (Must be a discord drawable), please just dm me on Discord or something.
11 |
12 | Alternatively, you may declare a field `pluginIcon` of type Drawable on your plugin and that will be used, e.g.:
13 |
14 | ```java
15 | public class DedicatedPluginSettings extends Plugin {
16 | private Drawable pluginIcon;
17 |
18 | public void load(Context ctx) {
19 | pluginIcon = ContextCompat.getDrawable(ctx, R$d.ic_theme_24dp);
20 | }
21 | }
22 | ```
23 |
--------------------------------------------------------------------------------
/DedicatedPluginSettings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.1.5"
2 | description = "Adds a dedicated plugin settings category to the settings page"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 1.1.4
7 | * Make entries reorderable via drag & drop
8 | * Add reset button
9 |
10 | # 1.1.3
11 | * Fix a small bug I missed
12 |
13 | # 1.1.2
14 | * Plugin section is now a dropdown that you can open/close
15 | * New Customize Option: You can now hide plugins from the list
16 |
17 | # 1.1.0
18 | * Add more icons
19 |
20 | # 1.0.5
21 | * Fix crash when changing device orientation
22 |
23 | # 1.0.4
24 | * Improve implementation
25 | """.trimIndent()
26 | )
27 |
--------------------------------------------------------------------------------
/DedicatedPluginSettings/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/DedicatedPluginSettings/src/main/java/dev/vendicated/aliucordplugs/dps/DragAndDropHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.dps
12 |
13 | import androidx.recyclerview.widget.ItemTouchHelper
14 | import androidx.recyclerview.widget.RecyclerView
15 | import java.util.*
16 |
17 | class DragAndDropHelper : ItemTouchHelper.Callback() {
18 | var isEnabled = false
19 |
20 | override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
21 | val flags = if (isEnabled) ItemTouchHelper.UP or ItemTouchHelper.DOWN else 0
22 | return makeMovementFlags(flags, 0)
23 | }
24 |
25 | override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
26 | val fromPos = viewHolder.adapterPosition
27 | val toPos = target.adapterPosition
28 | Collections.swap((recyclerView.adapter as PluginsAdapter).data, fromPos, toPos)
29 | recyclerView.adapter!!.notifyItemMoved(fromPos, toPos)
30 | return true
31 | }
32 |
33 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { }
34 | }
35 |
--------------------------------------------------------------------------------
/EmojiReplacer/README.md:
--------------------------------------------------------------------------------
1 | # EmojiReplacer
2 |
3 | Unfinished.
4 |
--------------------------------------------------------------------------------
/EmojiReplacer/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "0.0.1"
2 | description = "Punch 6pak for making me update 15 plugins"
3 |
4 | aliucord.excludeFromUpdaterJson.set(true)
5 |
--------------------------------------------------------------------------------
/EmojiReplacer/makezip.js:
--------------------------------------------------------------------------------
1 | const { promisify } = require("util");
2 | const _exec = promisify(require("child_process").exec);
3 | const fs = require("fs/promises");
4 | const path = require("path");
5 |
6 | async function main() {
7 | if (process.platform !== "linux") handleErr("This script only works on Linux.");
8 | const input = process.argv[2];
9 | const shouldResize = process.argv[3] === "true";
10 | const resolution = process.argv[4] || "72";
11 |
12 | await checkCommandExists("zip", "unzip");
13 | if (shouldResize) await checkCommandExists("convert");
14 |
15 | if (!input || !input.endsWith(".zip")) {
16 | handleErr(`Usage: ${process.argv[0]} ${process.argv[1]} [EMOJI_ZIP] [--resize RESOLUTION]`);
17 | }
18 |
19 | if (!(await exists(input))) handleErr("No such file: " + input);
20 |
21 | const workdir = await exec("mktemp -d");
22 | const extracted = path.join(workdir, "extracted");
23 | await fs.mkdir(extracted);
24 | await exec(`unzip -q ${input} -d ${extracted}`);
25 |
26 | const output = path.join(workdir, "output");
27 | await fs.mkdir(output);
28 |
29 | const data = require("./emojis.json");
30 |
31 | async function processEmoji({ surrogates, diversityChildren, parent }) {
32 | await Promise.all(diversityChildren?.map(c => processEmoji({ ...c, parent: surrogates })) ?? []);
33 | const codepoints = Array.from(surrogates).map(c => c.codePointAt(0).toString(16).padStart(4, "0"));
34 | let name = codepoints.join("-") + ".png";
35 | if (!(await exists(extracted, name))) {
36 | name = codepoints.filter(c => c !== "fe0f").join("-") + ".png";
37 | if (!(await exists(extracted, name))) {
38 | console.warn("Didn't find emoji " + surrogates);
39 | return;
40 | }
41 | }
42 | const outputName = `${parent ? `${parent}_` : ""}${surrogates}.png`;
43 | const inputFile = path.join(extracted, name);
44 | const outputFile = path.join(output, outputName);
45 | if (shouldResize) await exec(`convert ${inputFile} -resize ${resolution}x${resolution} ${outputFile}`);
46 | else await fs.rename(inputFile, outputFile);
47 | }
48 |
49 | await Promise.all(Object.values(data).flat().map(processEmoji));
50 | const interface = require("readline").createInterface({
51 | input: process.stdin,
52 | output: process.stdout,
53 | });
54 |
55 | console.log("\nDONE! Please paste the license below then CTRL+C:\n");
56 | const lines = [];
57 | interface.on("line", line => lines.push(line));
58 | interface.on("close", async () => {
59 | await fs.writeFile(path.join(output, "LICENSE.txt"), lines.join("\n"));
60 | await exec(`cd ${output} && zip -D -q -r ${path.join(__dirname, "output.zip")} *`);
61 | });
62 | interface.prompt();
63 | }
64 |
65 | async function checkCommandExists(...commands) {
66 | return Promise.all(
67 | commands.map(async cmd => {
68 | await _exec(`which ${cmd}`).catch(() => handleErr(`${cmd} not found. Please install it and try again`));
69 | })
70 | );
71 | }
72 |
73 | function exists(directory, file = "") {
74 | file = path.join(directory, file);
75 | return fs
76 | .access(file)
77 | .then(() => true)
78 | .catch(() => false);
79 | }
80 |
81 | function handleErr(err) {
82 | console.error(err);
83 | process.exit(1);
84 | }
85 |
86 | function exec(command) {
87 | console.info("Running " + command);
88 | return _exec(command)
89 | .then(({ stdout, stderr }) => {
90 | if (stderr) handleErr(stderr);
91 | if (stdout) console.log(stdout);
92 | return stdout.trim();
93 | })
94 | .catch(handleErr);
95 | }
96 |
97 | main();
98 |
--------------------------------------------------------------------------------
/EmojiReplacer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/EmojiReplacer/src/main/kotlin/dev/vendicated/aliucordplugins/emojireplacer/EmojiReplacer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.emojireplacer
12 |
13 | import android.content.Context
14 | import com.aliucord.Logger
15 | import com.aliucord.annotations.AliucordPlugin
16 | import com.aliucord.entities.Plugin
17 | import com.aliucord.patcher.Hook
18 | import com.aliucord.settings.delegate
19 | import com.discord.models.domain.emoji.ModelEmojiUnicode
20 | import com.discord.stores.StoreStream
21 |
22 | val logger = Logger("EmojiReplacer")
23 | @AliucordPlugin
24 | class EmojiReplacer : Plugin() {
25 | private val activePack : String by settings.delegate("none")
26 |
27 | init {
28 | settingsTab = SettingsTab(Settings::class.java)
29 | }
30 |
31 | override fun start(ctx: Context) {
32 | patcher.patch(ModelEmojiUnicode::class.java.getDeclaredMethod("getImageUri", String::class.java, Context::class.java), Hook { param ->
33 | val emoji = StoreStream.getEmojis().unicodeEmojiSurrogateMap[param.args[0]] ?: return@Hook
34 | logger.debug(emoji.toString())
35 | })
36 | }
37 |
38 | private val emojis = HashMap()
39 |
40 | override fun stop(context: Context) {
41 | patcher.unpatchAll()
42 | commands.unregisterAll()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/EmojiReplacer/src/main/kotlin/dev/vendicated/aliucordplugins/emojireplacer/Util.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.emojireplacer
12 |
13 | import android.content.Context
14 | import java.io.File
15 | import java.io.FileNotFoundException
16 | import java.util.zip.ZipFile
17 |
18 | fun File.extract(outputDir: File) {
19 | if (!outputDir.exists() && !outputDir.mkdirs()) throw RuntimeException("Failed to create directory $outputDir")
20 |
21 | ZipFile(this).use { zip ->
22 | zip.entries().asSequence().forEach {
23 | val isDir = it.isDirectory
24 | val file = File(outputDir, it.name)
25 | val dir = if (isDir) file else file.parentFile
26 | if (!dir.isDirectory && !dir.mkdirs()) throw RuntimeException("Failed to create directory $outputDir")
27 | if (!isDir) {
28 | zip.getInputStream(it).use { zis -> zis.copyTo(file.outputStream()) }
29 | }
30 | }
31 | }
32 | }
33 |
34 | val Context.emojiDir
35 | get() = File(filesDir, "emoji-replacer")
36 |
37 | val Context.installedPacks: Array
38 | get() = emojiDir.run {
39 | if (!exists() && !mkdirs()) throw FileNotFoundException()
40 | listFiles()!!
41 | }
42 |
43 |
44 | val File.folderSize: Long
45 | get() {
46 | var size = 0L
47 | for (file in listFiles()!!) {
48 | size += if (file.isDirectory) file.folderSize
49 | else file.length()
50 | }
51 | return size
52 | }
53 |
--------------------------------------------------------------------------------
/EmojiUtility/README.md:
--------------------------------------------------------------------------------
1 | # EmojiUtility - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/EmojiUtility.zip?raw=true)
2 |
3 | Adds lots of emoji utilities. All features are enabled by default but toggleable in the plugin settings.
4 |
5 | Emojis are saved to Download/Emojis/
6 |
7 |
8 | ## Features
9 |
10 | - New buttons in emoji widget (info sheet that opens if you click on one):
11 | - Copy emoji url
12 | - Download emoji
13 | - Clone emoji to other servers
14 | - New Commands:
15 | - download: Save all specified emotes, all emotes from the current server or all emotes of all servers you're on
16 | - TODO: wordreact: React with letter emojis matching the specified word / sentence
17 | - Hide unusable emojis
18 | - Keep reaction modal open after reacting if reaction button was long pressed
19 |
20 | ## Screenshots
21 |
22 | ### New Buttons in emoji widget
23 |
24 | 
25 |
26 | ### Clone Modal
27 |
28 | 
29 |
30 | ### Commands
31 |
32 | 
33 |
34 | 
35 |
--------------------------------------------------------------------------------
/EmojiUtility/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.2.2"
2 | description = "Adds lots of utility for emojis"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 1.2.2
7 | * Fix opening emoji sheet from reaction sheet for built-in emojis
8 |
9 | # 1.2.1
10 | * "Hide unusable emotes" now also hides unavailable emotes (due to Boost running out or similar)
11 |
12 | # 1.2.0
13 | * You can now view or download reaction emojis by opening the Reaction Sheet and long pressing the desired emoji
14 | """.trimIndent()
15 | )
16 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/java/dev/vendicated/aliucordplugs/emojiutility/EmojiUtility.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.emojiutility;
12 |
13 | import android.content.Context;
14 |
15 | import com.aliucord.Logger;
16 | import com.aliucord.annotations.AliucordPlugin;
17 | import com.aliucord.api.SettingsAPI;
18 | import com.aliucord.entities.Plugin;
19 |
20 | @AliucordPlugin
21 | public class EmojiUtility extends Plugin {
22 | public static boolean useHumanNames;
23 | public static final Logger logger = new Logger("EmojiUtility");
24 | public static SettingsAPI mSettings;
25 |
26 | public EmojiUtility() {
27 | settingsTab = new SettingsTab(Settings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings, patcher, commands);
28 | }
29 |
30 | @Override
31 | public void start(Context ctx) throws Throwable {
32 | mSettings = settings;
33 | useHumanNames = settings.getBool("humanNames", true);
34 | Patches.init(settings, patcher);
35 | Commands.registerAll(commands);
36 | }
37 |
38 | @Override
39 | public void stop(Context context) {
40 | patcher.unpatchAll();
41 | commands.unregisterAll();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/java/dev/vendicated/aliucordplugs/emojiutility/Settings.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.emojiutility;
12 |
13 | import android.content.Context;
14 | import android.content.DialogInterface;
15 | import android.os.Bundle;
16 | import android.os.Environment;
17 | import android.text.Editable;
18 | import android.text.TextWatcher;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.widget.LinearLayout;
22 |
23 | import androidx.annotation.NonNull;
24 |
25 | import com.aliucord.Utils;
26 | import com.aliucord.api.*;
27 | import com.aliucord.utils.DimenUtils;
28 | import com.aliucord.views.TextInput;
29 | import com.aliucord.widgets.BottomSheet;
30 | import com.discord.views.CheckedSetting;
31 |
32 | import java.io.File;
33 |
34 | import kotlin.jvm.functions.Function1;
35 |
36 | public class Settings extends BottomSheet {
37 | private final SettingsAPI settings;
38 | private final PatcherAPI patcher;
39 | private final CommandsAPI commands;
40 |
41 | public Settings(SettingsAPI settings, PatcherAPI patcher, CommandsAPI commands) {
42 | this.settings = settings;
43 | this.patcher = patcher;
44 | this.commands = commands;
45 | }
46 |
47 | @Override
48 | public void onViewCreated(View view, Bundle bundle) {
49 | super.onViewCreated(view, bundle);
50 |
51 | var ctx = requireContext();
52 | addInput(ctx, "Maximum Download Thread Count", "threadCount", "10", true, s -> {
53 | try {
54 | int i = Integer.parseInt(s);
55 | return i > 0 && i <= 100;
56 | } catch (NumberFormatException ignored) {}
57 | return false;
58 | });
59 | addInput(ctx, "Download Dir", "downloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/Emojis", false, s -> {
60 | var file = new File(s);
61 | return file.isDirectory() && file.canWrite();
62 | });
63 | addCheckedSetting(ctx, "Human readable file names", "Name downloads NAME.png instead of ID.png. Downloading twice will lead to duplicate downloads", "humanNames");
64 | addCheckedSetting(ctx, "Add extra buttons to emoji widget", "copy url, save & clone", "extraButtons");
65 | addCheckedSetting(ctx, "Keep reaction emoji picker open", "long press the react button","keepOpen");
66 | addCheckedSetting(ctx, "Hide unusable emojis", null, "hideUnusable");
67 | addCheckedSetting(ctx, "Register download commands", null, "registerCommands");
68 | }
69 |
70 | @Override
71 | public void onCancel(@NonNull DialogInterface dialog) {
72 | super.onCancel(dialog);
73 |
74 | if (settings.getBool("registerCommands", true))
75 | Commands.registerAll(commands);
76 | else
77 | commands.unregisterAll();
78 |
79 | EmojiUtility.useHumanNames = settings.getBool("humanNames", true);
80 | try {
81 | Patches.init(settings, patcher);
82 | } catch (Throwable th) {
83 | EmojiUtility.logger.error("Something went wrong while initialising the patches :(", th);
84 | }
85 | }
86 |
87 | private void addCheckedSetting(Context ctx, String title, String subtitle, String setting) {
88 | var cs = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, subtitle);
89 | cs.setChecked(settings.getBool(setting, true));
90 | cs.setOnCheckedListener(checked -> settings.setBool(setting, checked));
91 | addView(cs);
92 | }
93 |
94 | private void addInput(Context ctx, String title, String setting, String def, boolean isInt, Function1 validate) {
95 | var input = new TextInput(ctx);
96 | var params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
97 | params.setMargins(DimenUtils.getDefaultPadding(), DimenUtils.getDefaultPadding() / 2, DimenUtils.getDefaultPadding(), DimenUtils.getDefaultPadding() / 2);
98 | input.setLayoutParams(params);
99 | input.setHint(title);
100 | var editText = input.getEditText();
101 | editText.setText(isInt ? Integer.toString(settings.getInt(setting, Integer.parseInt(def))) : settings.getString(setting, def));
102 | editText.addTextChangedListener(new TextWatcher() {
103 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
104 | @Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
105 | @Override public void afterTextChanged(Editable e) {
106 | var s = e.toString();
107 | if (!validate.invoke(s)) input.setHint(title + " [INVALID]");
108 | else {
109 | if (isInt) {
110 | settings.setInt(setting, Integer.parseInt(s));
111 | } else {
112 | settings.setString(setting, s);
113 | }
114 | input.setHint(title);
115 | }
116 | }
117 | });
118 | addView(input);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/java/dev/vendicated/aliucordplugs/emojiutility/clonemodal/Adapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.emojiutility.clonemodal;
12 |
13 | import android.view.*;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.recyclerview.widget.RecyclerView;
17 |
18 | import com.aliucord.Utils;
19 | import com.discord.models.guild.Guild;
20 | import com.discord.utilities.extensions.SimpleDraweeViewExtensionsKt;
21 | import com.lytefast.flexinput.R;
22 |
23 | import java.util.List;
24 |
25 | public class Adapter extends RecyclerView.Adapter {
26 | private static final int layoutId = Utils.getResId("widget_user_profile_adapter_item_server", "layout");
27 |
28 | private final List guilds;
29 | private final Modal modal;
30 |
31 | public Adapter(Modal modal, List guilds) {
32 | this.guilds = guilds;
33 | this.modal = modal;
34 | }
35 |
36 | @Override
37 | public int getItemCount() {
38 | return guilds.size();
39 | }
40 |
41 | @NonNull
42 | @Override
43 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
44 | var layout = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
45 | return new ViewHolder(this, (ViewGroup) layout);
46 | }
47 |
48 | @Override
49 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
50 | var guild = guilds.get(position);
51 |
52 | if (guild.getIcon() != null) {
53 | var color = holder.icon.getContext().getColor(R.c.primary_dark_600);
54 | SimpleDraweeViewExtensionsKt.setGuildIcon(holder.icon, false, guild, 0, null, color, null, null, true, null);
55 | holder.iconText.setVisibility(View.GONE);
56 | } else {
57 | holder.icon.setVisibility(View.GONE);
58 | holder.iconText.setText(guild.getShortName());
59 | }
60 |
61 | holder.name.setText(guild.getName());
62 | }
63 |
64 | public void onClick(int position) {
65 | var guild = guilds.get(position);
66 | modal.clone(guild);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/java/dev/vendicated/aliucordplugs/emojiutility/clonemodal/Modal.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.emojiutility.clonemodal;
12 |
13 | import android.util.Base64;
14 | import android.view.View;
15 |
16 | import androidx.recyclerview.widget.LinearLayoutManager;
17 | import androidx.recyclerview.widget.RecyclerView;
18 |
19 | import com.aliucord.*;
20 | import com.aliucord.fragments.SettingsPage;
21 | import com.aliucord.utils.RxUtils;
22 | import com.aliucord.wrappers.GuildEmojiWrapper;
23 | import com.discord.api.permission.Permission;
24 | import com.discord.models.guild.Guild;
25 | import com.discord.restapi.RestAPIParams;
26 | import com.discord.stores.StoreGuilds;
27 | import com.discord.stores.StoreStream;
28 | import com.discord.utilities.permissions.PermissionUtils;
29 | import com.discord.utilities.rest.RestAPI;
30 |
31 | import java.io.ByteArrayOutputStream;
32 | import java.io.IOException;
33 | import java.util.HashMap;
34 | import java.util.Map;
35 |
36 | import dev.vendicated.aliucordplugs.emojiutility.EmojiUtility;
37 |
38 | public class Modal extends SettingsPage {
39 | private static final Map emojiLimits = new HashMap<>();
40 | static {
41 | emojiLimits.put(0, 50);
42 | emojiLimits.put(1, 100);
43 | emojiLimits.put(2, 150);
44 | emojiLimits.put(3, 250);
45 | }
46 |
47 | private final Map guildPerms = StoreStream.getPermissions().getGuildPermissions();
48 | private final StoreGuilds guildStore = StoreStream.getGuilds();
49 |
50 | private final String name;
51 | private final String url;
52 | private final long id;
53 | private final boolean isAnimated;
54 |
55 | public Modal(String url, String name, long id, boolean isAnimated) {
56 | this.url = url;
57 | this.name = name;
58 | this.id = id;
59 | this.isAnimated = isAnimated;
60 | }
61 |
62 | @Override
63 | public void onViewBound(View view) {
64 | super.onViewBound(view);
65 |
66 | setActionBarTitle("Clone Emoji");
67 | setActionBarSubtitle(name);
68 |
69 | var ctx = view.getContext();
70 |
71 | setPadding(0);
72 |
73 | var recycler = new RecyclerView(ctx);
74 | recycler.setLayoutManager(new LinearLayoutManager(ctx, RecyclerView.VERTICAL, false));
75 | var adapter = new Adapter(this, CollectionUtils.filter(guildStore.getGuilds().values(), this::isCandidate));
76 | recycler.setAdapter(adapter);
77 |
78 | addView(recycler);
79 | }
80 |
81 | private boolean isCandidate(Guild guild) {
82 | var perms = guildPerms.get(guild.getId());
83 | if (!PermissionUtils.can(Permission.MANAGE_EMOJIS_AND_STICKERS, perms)) return false;
84 | int usedSlots = 0;
85 | for (var emoji : guild.getEmojis()) {
86 | if (GuildEmojiWrapper.getId(emoji) == id) return false;
87 | if (GuildEmojiWrapper.isAnimated(emoji) == isAnimated) usedSlots++;
88 | }
89 | var slots = emojiLimits.get(guild.getPremiumTier());
90 | return slots != null && usedSlots < slots;
91 | }
92 |
93 | private String imageToDataUri() {
94 | try {
95 | var res = new Http.Request(url).execute();
96 | try (var baos = new ByteArrayOutputStream()) {
97 | res.pipe(baos);
98 | var b64 = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
99 | return String.format("data:image/%s;base64,%s", isAnimated ? "gif" : "png", b64);
100 | }
101 | } catch (IOException ex) { EmojiUtility.logger.error(ex); return null; }
102 | }
103 |
104 | public void clone(Guild guild) {
105 | Utils.threadPool.execute(() -> {
106 | var api = RestAPI.getApi();
107 | var uri = imageToDataUri();
108 | if (uri == null) {
109 | EmojiUtility.logger.errorToast("Something went wrong while preparing the image");
110 | return;
111 | }
112 | var obs = api.postGuildEmoji(guild.getId(), new RestAPIParams.PostGuildEmoji(name, uri));
113 | var res = RxUtils.await(obs);
114 | if (res.getSecond() == null)
115 | EmojiUtility.logger.infoToast(String.format("Successfully cloned %s to %s", name, guild.getName()));
116 | else
117 | EmojiUtility.logger.errorToast("Something went wrong while cloning this emoji", res.getSecond());
118 | });
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/EmojiUtility/src/main/java/dev/vendicated/aliucordplugs/emojiutility/clonemodal/ViewHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.emojiutility.clonemodal;
12 |
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.TextView;
16 |
17 | import androidx.annotation.NonNull;
18 | import androidx.recyclerview.widget.RecyclerView;
19 |
20 | import com.aliucord.Utils;
21 | import com.facebook.drawee.view.SimpleDraweeView;
22 |
23 | public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
24 | private static final int iconId = Utils.getResId("user_profile_adapter_item_server_image", "id");
25 | private static final int iconTextId = Utils.getResId("user_profile_adapter_item_server_text", "id");
26 | private static final int serverNameId = Utils.getResId("user_profile_adapter_item_server_name", "id");
27 | private static final int identityBarrierId = Utils.getResId("guild_member_identity_barrier", "id");
28 | private static final int serverAvatarId = Utils.getResId("guild_member_avatar", "id");
29 | private static final int serverNickId = Utils.getResId("user_profile_adapter_item_user_display_name", "id");
30 |
31 | private final Adapter adapter;
32 |
33 | public final SimpleDraweeView icon;
34 | public final TextView iconText;
35 | public final TextView name;
36 |
37 | public ViewHolder(Adapter adapter, @NonNull ViewGroup layout) {
38 | super(layout);
39 | this.adapter = adapter;
40 |
41 | icon = layout.findViewById(iconId);
42 | iconText = layout.findViewById(iconTextId);
43 | name = layout.findViewById(serverNameId);
44 |
45 | // Hide server profile stuff
46 | layout.findViewById(identityBarrierId).setVisibility(View.GONE);
47 | layout.findViewById(serverAvatarId).setVisibility(View.GONE);
48 | layout.findViewById(serverNickId).setVisibility(View.GONE);
49 |
50 | layout.setOnClickListener(this);
51 | }
52 |
53 | @Override public void onClick(View view) {
54 | adapter.onClick(getAdapterPosition());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/FixEmotes/README.md:
--------------------------------------------------------------------------------
1 | # FixEmotes - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/FixEmotes.zip?raw=true)
2 |
3 | Fixes some emotes being rendered unusable if you have two emotes with the same name but different casing
4 |
--------------------------------------------------------------------------------
/FixEmotes/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.1"
2 | description = "Fixes some emotes being unusable if you have two emotes with the same name but different casing"
3 |
4 | aliucord.changelog.set("""
5 | # 1.0.1
6 | - Fix for Discord 120.11
7 | - Should now 100% accurately replace emotes with the correct emoji (and not a random emoji with that same name)
8 | """.trimIndent())
9 |
--------------------------------------------------------------------------------
/FixEmotes/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/FixEmotes/src/main/kotlin/dev/vendicated/aliucordplugins/fixemotes/FixEmotes.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.fixemotes
12 |
13 | import android.content.Context
14 | import com.aliucord.annotations.AliucordPlugin
15 | import com.aliucord.entities.Plugin
16 | import com.aliucord.patcher.PreHook
17 | import com.aliucord.patcher.after
18 | import com.discord.restapi.RestAPIParams
19 | import com.discord.widgets.chat.input.emoji.EmojiPickerViewModel
20 |
21 | @AliucordPlugin
22 | class FixEmotes : Plugin() {
23 | private val brokenEmoteRegex = "(?("handleStoreState", EmojiPickerViewModel.StoreState::class.java) { param ->
28 | (param.args[0] as? EmojiPickerViewModel.StoreState.Emoji)?.let {
29 | storeState = it
30 | }
31 | }
32 |
33 | val ctor = RestAPIParams.Message::class.java.declaredConstructors.firstOrNull {
34 | !it.isSynthetic
35 | } ?: throw IllegalStateException("Didn't find RestAPIParams.Message ctor")
36 |
37 | patcher.patch(
38 | ctor,
39 | PreHook
40 | { param ->
41 | param.args[0] = param.args[0].toString().replace(brokenEmoteRegex) {
42 | storeState?.emojiSet?.customEmojis?.forEach { (_, g) ->
43 | g.forEach { e ->
44 | if (e.getCommand("" /* not used, only there to implement method lmao */) == it.value) {
45 | return@replace e.messageContentReplacement
46 | }
47 | }
48 | }
49 | it.value
50 | }
51 | })
52 | }
53 |
54 | override fun stop(context: Context) {
55 | patcher.unpatchAll()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/GatewayLog/README.md:
--------------------------------------------------------------------------------
1 | # GatewayLog - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/GatewayLog.zip?raw=true)
2 |
3 | Logs all gateway messages to log files in Aliucord folder.
4 |
5 | Only useful for developers, also stores your token in plain text world readable file, so don't install unless you know what you're doing.
6 |
--------------------------------------------------------------------------------
/GatewayLog/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.0"
2 | description = "Logs all gateway messages to log files in the Aliucord folder for debugging purposes"
3 |
4 | aliucord.excludeFromUpdaterJson.set(true)
5 |
--------------------------------------------------------------------------------
/GatewayLog/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/GatewayLog/src/main/kotlin/dev/vendicated/aliucordplugins/gatewaylog/GatewayLog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.gatewaylog
12 |
13 | import android.content.Context
14 | import com.aliucord.Constants
15 | import com.aliucord.annotations.AliucordPlugin
16 | import com.aliucord.entities.Plugin
17 | import com.aliucord.utils.ReflectUtils
18 | import com.discord.gateway.GatewaySocketLogger
19 | import com.discord.stores.StoreStream
20 | import com.discord.utilities.logging.AppGatewaySocketLogger
21 | import java.io.File
22 | import java.io.FileOutputStream
23 |
24 | @AliucordPlugin
25 | class GatewayLog : Plugin() {
26 | private lateinit var inbound: FileOutputStream
27 | private lateinit var outbound: FileOutputStream
28 |
29 | override fun start(ctx: Context) {
30 | val base = File(Constants.BASE_PATH)
31 | inbound = FileOutputStream(File(base, "gateway_inbound.txt"))
32 | outbound = FileOutputStream(File(base, "gateway_outbound.txt"))
33 |
34 | ReflectUtils.setField(ReflectUtils.getField(StoreStream.getGatewaySocket(), "socket")!!, "gatewaySocketLogger", object : GatewaySocketLogger {
35 | override fun getLogLevel(): GatewaySocketLogger.LogLevel {
36 | return GatewaySocketLogger.LogLevel.VERBOSE
37 | }
38 | override fun logInboundMessage(str: String) {
39 | inbound.write((str + "\n").toByteArray())
40 | }
41 | override fun logMessageInflateFailed(th: Throwable) {
42 | logger.error("Error inflating gateway message (Exception comes from Discord)", th)
43 | }
44 | override fun logOutboundMessage(str: String) {
45 | outbound.write((str + "\n").toByteArray())
46 | }
47 | })
48 | }
49 |
50 | override fun stop(context: Context) {
51 | ReflectUtils.setField(ReflectUtils.getField(StoreStream.getGatewaySocket(), "socket")!!, "gatewaySocketLogger", (AppGatewaySocketLogger.Companion).instance)
52 | inbound.close()
53 | outbound.close()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Hastebin/README.md:
--------------------------------------------------------------------------------
1 | # Hastebin - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/Hastebin.zip?raw=true)
2 |
3 | Create pastes on your favourite Hastebin Mirror
4 |
5 | You can customise the hastebin mirror in the plugin settings
6 |
--------------------------------------------------------------------------------
/Hastebin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.4"
2 | description = "Create pastes on hastebin"
3 |
--------------------------------------------------------------------------------
/Hastebin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Hastebin/src/main/java/dev/vendicated/aliucordplugs/hastebin/HasteResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.hastebin;
12 |
13 | public class HasteResponse {
14 | public String key;
15 | }
16 |
--------------------------------------------------------------------------------
/Hastebin/src/main/java/dev/vendicated/aliucordplugs/hastebin/Hastebin.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.hastebin;
12 |
13 | import android.content.Context;
14 |
15 | import com.aliucord.Http;
16 | import com.aliucord.Utils;
17 | import com.aliucord.annotations.AliucordPlugin;
18 | import com.aliucord.api.CommandsAPI;
19 | import com.aliucord.entities.Plugin;
20 | import com.discord.api.commands.ApplicationCommandType;
21 |
22 | import java.io.IOException;
23 | import java.util.Arrays;
24 |
25 | @AliucordPlugin
26 | public class Hastebin extends Plugin {
27 | public Hastebin() {
28 | super();
29 | settingsTab = new SettingsTab(PluginSettings.class).withArgs(settings);
30 | }
31 |
32 | @Override
33 | public void start(Context context) {
34 | var arguments = Arrays.asList(
35 | Utils.createCommandOption(ApplicationCommandType.STRING, "text", "The text to upload", null, true),
36 | Utils.createCommandOption(ApplicationCommandType.BOOLEAN, "send", "Whether the message should be visible for everyone")
37 | );
38 |
39 | commands.registerCommand(
40 | "haste",
41 | "Create pastes on hastebin",
42 | arguments,
43 | ctx -> {
44 | var text = ctx.getRequiredString("text");
45 | var send = ctx.getBoolOrDefault("send", false);
46 |
47 | String result;
48 | String mirror = settings.getString("mirror", "https://haste.powercord.dev") + "/";
49 |
50 | try {
51 | HasteResponse res = Http.simpleJsonPost(mirror + "documents", text, HasteResponse.class);
52 | result = mirror + res.key;
53 | } catch (IOException ex) {
54 | send = false;
55 | result = String.format("Error while uploading to hastebin:\n```\n%s```Consider changing hastebin mirror if this keeps happening", ex.getMessage());
56 | }
57 |
58 | return new CommandsAPI.CommandResult(result, null, send);
59 | }
60 | );
61 | }
62 |
63 | @Override
64 | public void stop(Context context) {
65 | commands.unregisterAll();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Hastebin/src/main/java/dev/vendicated/aliucordplugs/hastebin/PluginSettings.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.hastebin;
12 |
13 | import android.annotation.SuppressLint;
14 | import android.text.Editable;
15 | import android.text.TextWatcher;
16 | import android.view.View;
17 |
18 | import com.aliucord.Utils;
19 | import com.aliucord.api.SettingsAPI;
20 | import com.aliucord.fragments.SettingsPage;
21 | import com.aliucord.views.Button;
22 | import com.aliucord.views.TextInput;
23 |
24 | import java.util.regex.Pattern;
25 |
26 |
27 | @SuppressLint("SetTextI18n")
28 | public final class PluginSettings extends SettingsPage {
29 | private static final Pattern re = Pattern.compile("https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}");
30 |
31 | private final SettingsAPI settings;
32 |
33 | public PluginSettings(SettingsAPI settings) {
34 | this.settings = settings;
35 | }
36 |
37 | @Override
38 | public void onViewBound(View view) {
39 | super.onViewBound(view);
40 |
41 | setActionBarTitle("Hastebin");
42 |
43 | var ctx = requireContext();
44 |
45 | var input = new TextInput(ctx);
46 | input.setHint("Hastebin Mirror");
47 |
48 | var editText = input.getEditText();
49 |
50 | var button = new Button(ctx);
51 | button.setText("Totally wrong text oh no i better update this");
52 | button.setOnClickListener(v -> {
53 | settings.setString("mirror", editText.getText().toString().replaceFirst("/+$", ""));
54 | Utils.showToast("Saved!");
55 | close();
56 | });
57 |
58 | editText.setMaxLines(1);
59 | editText.setText(settings.getString("mirror", "https://haste.powercord.dev"));
60 | editText.addTextChangedListener(new TextWatcher() {
61 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
62 | public void onTextChanged(CharSequence s, int start, int before, int count) { }
63 | public void afterTextChanged(Editable s) {
64 | if (!isValid(s.toString())) {
65 | button.setAlpha(0.5f);
66 | button.setClickable(false);
67 | } else {
68 | button.setAlpha(1f);
69 | button.setClickable(true);
70 | }
71 | }
72 | });
73 |
74 | addView(input);
75 | addView(button);
76 | }
77 |
78 | private boolean isValid(String s) {
79 | return re.matcher(s).matches();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/JsEval/README.md:
--------------------------------------------------------------------------------
1 | # Template - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/DeezNuts.zip?raw=true)
2 |
3 | This is the Template I generate all my plugins from. Not much to see here :)
4 |
--------------------------------------------------------------------------------
/JsEval/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.0"
2 | description = "Punch 6pak for making me update 15 plugins"
3 |
--------------------------------------------------------------------------------
/JsEval/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/JsEval/src/main/kotlin/dev/vendicated/aliucordplugins/jseval/CodeTextView.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.jseval
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.widget.TextView
16 | import androidx.core.content.res.ResourcesCompat
17 | import com.aliucord.Constants
18 | import com.lytefast.flexinput.R
19 |
20 | @SuppressLint("AppCompatCustomView")
21 | class CodeTextView(ctx : Context) : TextView(ctx, null, 0, R.i.UiKit_TextView) {
22 | init {
23 | typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.sourcecodepro_semibold)
24 | setTextIsSelectable(true)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/JsEval/src/main/kotlin/dev/vendicated/aliucordplugins/jseval/JsEval.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.jseval
12 |
13 | import android.content.Context
14 | import com.aliucord.annotations.AliucordPlugin
15 | import com.aliucord.entities.Plugin
16 |
17 | @AliucordPlugin
18 | class JsEval : Plugin() {
19 | override fun start(ctx: Context) {
20 | settingsTab = SettingsTab(TerminalPage::class.java)
21 | }
22 |
23 | override fun stop(context: Context) {
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/JsEval/src/main/kotlin/dev/vendicated/aliucordplugins/jseval/TerminalButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.jseval
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.graphics.Color
16 | import android.graphics.Paint
17 | import android.graphics.drawable.ShapeDrawable
18 | import android.graphics.drawable.shapes.RectShape
19 | import android.widget.LinearLayout
20 | import androidx.appcompat.widget.AppCompatImageButton
21 | import androidx.core.content.ContextCompat
22 | import com.aliucord.utils.DimenUtils.dp
23 |
24 | @SuppressLint("ViewConstructor")
25 | class TerminalButton(ctx: Context, drawableId: Int, onClick: OnClickListener) : AppCompatImageButton(ctx) {
26 | init {
27 | layoutParams = LinearLayout.LayoutParams(30.dp, 30.dp).apply {
28 | setMargins(6.dp, 0, 6.dp, 0)
29 | }
30 | setPadding(6.dp, 6.dp, 6.dp, 6.dp)
31 |
32 | background = ShapeDrawable().apply {
33 | shape = RectShape()
34 | paint.color = Color.GRAY
35 | paint.strokeWidth = 2f
36 | paint.style = Paint.Style.STROKE
37 | }
38 |
39 | ContextCompat.getDrawable(ctx, drawableId)!!.run {
40 | mutate()
41 | setTint(Color.WHITE)
42 | setImageDrawable(this)
43 | }
44 | setOnClickListener(onClick)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/JsEval/src/main/kotlin/dev/vendicated/aliucordplugins/jseval/TerminalHistoryAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.jseval
12 |
13 | import android.annotation.SuppressLint
14 | import android.graphics.Color
15 | import android.view.Gravity
16 | import android.view.ViewGroup
17 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
18 | import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
19 | import android.widget.LinearLayout
20 | import android.widget.TextView
21 | import androidx.core.content.ContextCompat
22 | import androidx.recyclerview.widget.RecyclerView
23 | import com.lytefast.flexinput.R
24 |
25 | abstract class HistoryItem(val viewType: Int)
26 | enum class ViewType {
27 | RESULT, EMPTY
28 | }
29 | data class ResultItem(val input: String, val output: String, val isError: Boolean) : HistoryItem(ViewType.RESULT.ordinal)
30 | data class EmptyItem(val text: String, val isError: Boolean) : HistoryItem(ViewType.EMPTY.ordinal)
31 |
32 | class TerminalHistoryAdapter : RecyclerView.Adapter() {
33 | private val data = ArrayList()
34 |
35 | override fun getItemViewType(position: Int) = data[position].viewType
36 |
37 | override fun getItemCount() = data.size
38 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
39 | when (viewType) {
40 | ViewType.RESULT.ordinal -> ResultEntry.create(parent)
41 | ViewType.EMPTY.ordinal -> EmptyEntry.create(parent)
42 | else -> throw NotImplementedError()
43 | }
44 |
45 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
46 | when (val item = data[position]) {
47 | is ResultItem -> {
48 | (holder as ResultEntry).bindView(item.input, item.output, item.isError)
49 | }
50 | is EmptyItem -> {
51 | (holder as EmptyEntry).bindView(item.text, item.isError)
52 | }
53 | }
54 | }
55 |
56 | fun append(item: HistoryItem): Int {
57 | data.add(item)
58 | notifyItemInserted(data.size - 1)
59 | return data.size - 1
60 | }
61 |
62 | @SuppressLint("NotifyDataSetChanged")
63 | fun clear() {
64 | data.clear()
65 | notifyDataSetChanged()
66 | }
67 | }
68 |
69 | class EmptyEntry(private val textView: TextView) : RecyclerView.ViewHolder(textView) {
70 | fun bindView(text: String, isError: Boolean) {
71 | textView.text = text
72 | textView.setTextColor(if (isError) Color.RED else Color.WHITE)
73 | }
74 |
75 | companion object {
76 | fun create(parent: ViewGroup): EmptyEntry {
77 | val textView = CodeTextView(parent.context).apply {
78 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
79 | // textAlignment = View.TEXT_ALIGNMENT_CENTER
80 | }
81 | return EmptyEntry(textView)
82 | }
83 | }
84 | }
85 |
86 | class ResultEntry(layout: LinearLayout, private val promptView: TextView, private val resultView: TextView) : RecyclerView.ViewHolder(layout) {
87 | fun bindView(input: String, output: String, isError: Boolean) {
88 | promptView.text = input
89 | resultView.text = output
90 | resultView.setTextColor(if (isError) Color.RED else Color.WHITE)
91 | }
92 |
93 | companion object {
94 | fun create(parent: ViewGroup): ResultEntry {
95 | val ctx = parent.context
96 | val fullWidthParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
97 | val layout = LinearLayout(ctx).apply {
98 | orientation = LinearLayout.VERTICAL
99 | layoutParams = fullWidthParams
100 | }
101 | val promptView = CodeTextView(ctx).apply {
102 | ContextCompat.getDrawable(ctx, R.e.ic_arrow_right_24dp)!!.run {
103 | mutate()
104 | setTint(Color.WHITE)
105 | setCompoundDrawablesRelativeWithIntrinsicBounds(this, null, null, null)
106 | }
107 | layoutParams = fullWidthParams
108 | this.gravity = Gravity.CENTER_VERTICAL
109 | }
110 | val resultView = CodeTextView(ctx).apply {
111 | layoutParams = fullWidthParams
112 | // Add drawable with same colour as background to properly align text
113 | ContextCompat.getDrawable(ctx, R.e.ic_arrow_right_24dp)!!.run {
114 | mutate()
115 | setTint(Color.BLACK)
116 | setCompoundDrawablesRelativeWithIntrinsicBounds(this, null, null, null)
117 | }
118 | }
119 | return ResultEntry(layout, promptView, resultView).also {
120 | layout.addView(promptView)
121 | layout.addView(resultView)
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/MessageLinkEmbeds/README.md:
--------------------------------------------------------------------------------
1 | # MessageLinkEmbeds - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/MessageLinkEmbeds.zip?raw=true)
2 |
3 | Embeds discord message links
4 |
5 | 
--------------------------------------------------------------------------------
/MessageLinkEmbeds/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.4.5"
2 | description = "Embeds message links"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 1.4.4 & 1.4.5
7 | * Fix for new Discord versions
8 |
9 | # 1.4.3
10 | * Ignore message links
11 |
12 | # 1.4.2
13 | * Hopefully fix duplicate embeds
14 |
15 | # 1.4.0
16 | * Add support for files & embed fields
17 |
18 | # 1.3.0
19 | * Make message links jump directly in Discord instead of being launched like regular URLs
20 |
21 | """.trimIndent()
22 | )
23 |
--------------------------------------------------------------------------------
/MessageLinkEmbeds/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/PlayableEmbeds/README.md:
--------------------------------------------------------------------------------
1 | # PlayableEmbeds - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/PlayableEmbeds.zip?raw=true)
2 |
3 | Makes Youtube and Spotify embeds playable directly inside Discord.
4 |
5 |
6 | https://user-images.githubusercontent.com/45497981/142783818-deaea9b6-3a66-4627-a7b7-dc0a30c0ef49.mp4
7 |
--------------------------------------------------------------------------------
/PlayableEmbeds/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.2"
2 | description = "Makes Spotify and Youtube Embeds playable"
3 |
4 | aliucord.changelog.set("""
5 | # 1.0.2
6 | * Youtube embed now supports timestamps
7 | """.trimIndent())
8 |
--------------------------------------------------------------------------------
/PlayableEmbeds/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/PlayableEmbeds/src/main/kotlin/dev/vendicated/aliucordplugins/betterplatformembeds/PlayableEmbeds.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.betterplatformembeds
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.graphics.Color
16 | import android.view.*
17 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
18 | import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
19 | import android.webkit.WebView
20 | import android.widget.LinearLayout
21 | import androidx.cardview.widget.CardView
22 | import androidx.constraintlayout.widget.ConstraintLayout
23 | import com.aliucord.Utils
24 | import com.aliucord.annotations.AliucordPlugin
25 | import com.aliucord.entities.Plugin
26 | import com.aliucord.patcher.*
27 | import com.aliucord.wrappers.embeds.MessageEmbedWrapper.Companion.rawProvider
28 | import com.aliucord.wrappers.embeds.MessageEmbedWrapper.Companion.url
29 | import com.aliucord.wrappers.embeds.ProviderWrapper.Companion.name
30 | import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemEmbed
31 | import com.facebook.drawee.view.SimpleDraweeView
32 | import com.google.android.material.card.MaterialCardView
33 | import java.util.*
34 |
35 | class ScrollableWebView(ctx: Context) : WebView(ctx) {
36 | @SuppressLint("ClickableViewAccessibility")
37 | override fun onTouchEvent(event: MotionEvent?): Boolean {
38 | requestDisallowInterceptTouchEvent(true)
39 | return super.onTouchEvent(event)
40 | }
41 | }
42 |
43 | @AliucordPlugin
44 | class PlayableEmbeds : Plugin() {
45 | private val webviewMap = WeakHashMap()
46 | private val widgetId = View.generateViewId()
47 | private val spotifyUrlRe = Regex("https://open\\.spotify\\.com/(\\w+)/(\\w+)")
48 | private val youtubeUrlRe =
49 | Regex("(?:https?://)?(?:(?:www|m)\\.)?(?:youtu\\.be/|youtube(?:-nocookie)?\\.com/"+
50 | "(?:embed/|v/|watch\\?v=|watch\\?.+&v=|shorts/))((\\w|-){11})"+
51 | "(?:(?:\\?|&)(?:star)?t=(\\d+))?(?:\\S+)?")
52 |
53 | override fun start(_context: Context) {
54 | patcher.after("configureUI", WidgetChatListAdapterItemEmbed.Model::class.java) {
55 | val model = it.args[0] as WidgetChatListAdapterItemEmbed.Model
56 | val embed = model.embedEntry.embed
57 | val holder = it.thisObject as WidgetChatListAdapterItemEmbed
58 | val layout = holder.itemView as ConstraintLayout
59 |
60 | layout.findViewById(widgetId)?.let { v ->
61 | if (webviewMap[v] == embed.url) return@after
62 | (v.parent as ViewGroup).removeView(v)
63 | }
64 | val url = embed.url ?: return@after;
65 | when (embed.rawProvider?.name) {
66 | "YouTube" -> addYoutubeEmbed(layout, url)
67 | "Spotify" -> addSpotifyEmbed(layout, url)
68 | }
69 | }
70 | }
71 |
72 | private fun addYoutubeEmbed(layout: ViewGroup, url: String) {
73 | val ctx = layout.context
74 |
75 | val (_, videoId, _, timestamp) = youtubeUrlRe.find(url, 0).groupValues
76 |
77 | val cardView = layout.findViewById(Utils.getResId("embed_image_container", "id"))
78 | val chatListItemEmbedImage = cardView.findViewById(Utils.getResId("chat_list_item_embed_image", "id"))
79 | val playButton = cardView.findViewById(Utils.getResId("chat_list_item_embed_image_icons", "id"))
80 | playButton.visibility = View.GONE
81 | chatListItemEmbedImage.visibility = View.GONE
82 |
83 | val webView = ScrollableWebView(ctx).apply {
84 | id = widgetId
85 | setBackgroundColor(Color.TRANSPARENT)
86 | // val maxImgWidth = EmbedResourceUtils.INSTANCE.computeMaximumImageWidthPx(ctx)
87 | // val (width, height) = EmbedResourceUtils.INSTANCE.calculateScaledSize(1280, 720, maxImgWidth, maxImgWidth, ctx.resources, maxImgWidth / 2)
88 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
89 | @SuppressLint("SetJavaScriptEnabled")
90 | settings.javaScriptEnabled = true
91 |
92 | cardView.addView(this)
93 | }
94 | webviewMap[webView] = url
95 |
96 | webView.run {
97 | loadData(
98 | """
99 |
100 |
101 |
120 |
121 |
122 |
123 |
130 |
131 |
132 |
133 | """,
134 | "text/html",
135 | "UTF-8"
136 | )
137 | }
138 | }
139 |
140 | private fun addSpotifyEmbed(layout: ViewGroup, url: String) {
141 | val ctx = layout.context
142 |
143 | val (_, type, itemId) = spotifyUrlRe.find(url, 0).groupValues
144 | val embedUrl = "https://open.spotify.com/embed/$type/$itemId"
145 |
146 | val webView = (if (type == "track") WebView(ctx) else ScrollableWebView(ctx)).apply {
147 | id = widgetId
148 | setBackgroundColor(Color.TRANSPARENT)
149 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
150 | @SuppressLint("SetJavaScriptEnabled")
151 | settings.javaScriptEnabled = true
152 |
153 | val cardView = layout.findViewById(Utils.getResId("chat_list_item_embed_container_card", "id"))
154 | cardView.addView(this)
155 | }
156 |
157 | webviewMap[webView] = url
158 | webView.run {
159 | loadData(
160 | """
161 |
162 |
163 |
172 |
173 |
174 | """,
175 | "text/html",
176 | "UTF-8"
177 | )
178 | }
179 | }
180 |
181 | @Suppress("Unused")
182 | private fun addPornHubEmbed() {
183 |
184 | }
185 |
186 | override fun stop(context: Context) {
187 | patcher.unpatchAll()
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/PluginDownloader/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 |
3 | PluginDownload is now part of Aliucord. There is no need to install this plugin anymore.
4 |
5 | ___
6 |
7 | # PluginDownloader - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/PluginDownloader.zip?raw=true)
8 |
9 | Adds plugin downloader to all messages in #plugin-links and #plugin-links-updates on the Aliucord server
10 |
11 | Simply long press a message and the button will appear. Depending on the channel it will either open a Modal with a list of all available plugins or directly download the plugin
12 |
13 | Installed plugins are automatically loaded so a restart should usually not be necessary
14 |
15 | 
16 |
17 | 
18 |
--------------------------------------------------------------------------------
/PluginDownloader/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.2.5";
2 | description = "Adds message context menu items to quick download plugins";
3 |
--------------------------------------------------------------------------------
/PluginDownloader/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/PluginDownloader/src/main/java/dev/vendicated/aliucordplugs/plugindownloader/Modal.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.plugindownloader;
12 |
13 | import android.annotation.SuppressLint;
14 | import android.view.View;
15 | import android.widget.TextView;
16 |
17 | import com.aliucord.Http;
18 | import com.aliucord.Utils;
19 | import com.aliucord.fragments.SettingsPage;
20 | import com.aliucord.views.Button;
21 | import com.aliucord.views.DangerButton;
22 | import com.google.gson.reflect.TypeToken;
23 | import com.lytefast.flexinput.R;
24 |
25 | import java.io.PrintWriter;
26 | import java.io.StringWriter;
27 | import java.lang.reflect.Type;
28 | import java.util.*;
29 |
30 | public final class Modal extends SettingsPage {
31 | private static final Type resType = TypeToken.getParameterized(Map.class, String.class, Plugin.Info.class).getType();
32 |
33 | private final String author;
34 | private final String repo;
35 | private Map plugins;
36 | private Throwable ex;
37 |
38 | public Modal(String author, String repo) {
39 | super();
40 | this.author = author;
41 | this.repo = repo;
42 | }
43 |
44 | @Override
45 | @SuppressLint("SetTextI18n")
46 | public void onViewBound(View view) {
47 | super.onViewBound(view);
48 |
49 | setActionBarTitle("Plugin downloader");
50 | setActionBarSubtitle(String.format("https://github.com/%s/%s", author, repo));
51 |
52 | var ctx = requireContext();
53 |
54 | if (ex != null) {
55 | var exView = new TextView(ctx, null, 0, R.i.UiKit_Settings_Item_SubText);
56 | var sw = new StringWriter();
57 | var pw = new PrintWriter(sw);
58 | ex.printStackTrace(pw);
59 | exView.setText("An error occurred:\n\n" + sw);
60 | exView.setTextIsSelectable(true);
61 | addView(exView);
62 | } else if (plugins == null) {
63 | Utils.threadPool.execute(() -> {
64 | try {
65 | plugins = Http.simpleJsonGet(String.format("https://raw.githubusercontent.com/%s/%s/builds/updater.json", author, repo), resType);
66 | } catch (Throwable e) {
67 | ex = e;
68 | }
69 | Utils.mainThread.post(() -> onViewBound(view));
70 | });
71 | } else {
72 | var list = new ArrayList();
73 | for (var plugin : plugins.entrySet()) {
74 | String name = plugin.getKey();
75 | if (name.equals("default")) continue;
76 | boolean exists = PDUtil.isPluginInstalled(name);
77 | String title = String.format("%s %s v%s", exists ? "Uninstall" : "Install", name, plugin.getValue().version);
78 | list.add(new Plugin.CardInfo(name, title, exists));
79 | }
80 | list.sort(Comparator.comparing(a -> a.title));
81 | for (var plugin: list) {
82 | var button = plugin.exists ? new DangerButton(ctx) : new Button(ctx);
83 | button.setText(plugin.title);
84 | if (!plugin.exists)
85 | button.setOnClickListener(e -> PDUtil.downloadPlugin(ctx, author, repo, plugin.name, this::reRender));
86 | else
87 | button.setOnClickListener(e -> PDUtil.deletePlugin(ctx, plugin.name, this::reRender));
88 | addView(button);
89 | }
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/PluginDownloader/src/main/java/dev/vendicated/aliucordplugs/plugindownloader/PDUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.plugindownloader;
12 |
13 | import android.content.Context;
14 |
15 | import com.aliucord.Constants;
16 | import com.aliucord.Http;
17 | import com.aliucord.PluginManager;
18 | import com.aliucord.Utils;
19 |
20 | import java.io.File;
21 | import java.io.FileOutputStream;
22 | import java.io.IOException;
23 |
24 | public final class PDUtil {
25 | private static File openPluginFile(String plugin) {
26 | return new File(String.format("%s/plugins/%s.zip", Constants.BASE_PATH, plugin));
27 | }
28 |
29 | public static void downloadPlugin(Context ctx, String author, String repo, String name, Runnable callback) {
30 | Utils.threadPool.execute(() -> {
31 | var url = String.format("https://github.com/%s/%s/raw/builds/%s.zip", author, repo, name);
32 | var file = openPluginFile(name);
33 | if (file.exists()) {
34 | Utils.showToast(String.format("Plugin %s already installed", name));
35 | return;
36 | }
37 | try {
38 | var res = new Http.Request(url).execute();
39 | try (var out = new FileOutputStream(file)) {
40 | res.pipe(out);
41 | PluginManager.loadPlugin(ctx, file);
42 | PluginManager.startPlugin(name);
43 | Utils.showToast(String.format("Plugin %s successfully downloaded", name));
44 | Utils.mainThread.post(callback);
45 | }
46 | } catch (IOException ex) {
47 | Utils.showToast(String.format("Something went wrong while downloading plugin %s, sorry: %s", name, ex.getMessage()));
48 | if (file.exists())
49 | //noinspection ResultOfMethodCallIgnored
50 | file.delete();
51 | }
52 | });
53 | }
54 |
55 | public static boolean isPluginInstalled(String plugin) {
56 | return openPluginFile(plugin).exists();
57 | }
58 |
59 | public static void deletePlugin(Context ctx, String plugin, Runnable callback) {
60 | boolean success = openPluginFile(plugin).delete();
61 | Utils.showToast(ctx, String.format("%s plugin %s", success ? "Successfully uninstalled" : "Failed to uninstall", plugin));
62 | if (success) {
63 | PluginManager.stopPlugin(plugin);
64 | Utils.mainThread.post(callback);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/PluginDownloader/src/main/java/dev/vendicated/aliucordplugs/plugindownloader/Plugin.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.plugindownloader;
12 |
13 | public class Plugin {
14 | public static class Info {
15 | public String version;
16 | public int minimumDiscordVersion;
17 | }
18 | public static class CardInfo {
19 | public final String name;
20 | public final String title;
21 | public final boolean exists;
22 | public CardInfo(String name, String title, boolean exists) {
23 | this.name = name;
24 | this.title = title;
25 | this.exists = exists;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PluginDownloader/src/main/java/dev/vendicated/aliucordplugs/plugindownloader/PluginDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.plugindownloader;
12 |
13 | import android.annotation.SuppressLint;
14 | import android.content.Context;
15 | import android.view.View;
16 | import android.widget.LinearLayout;
17 | import android.widget.TextView;
18 |
19 | import androidx.core.content.ContextCompat;
20 | import androidx.core.widget.NestedScrollView;
21 |
22 | import com.aliucord.Constants;
23 | import com.aliucord.Utils;
24 | import com.aliucord.annotations.AliucordPlugin;
25 | import com.aliucord.entities.Plugin;
26 | import com.aliucord.patcher.Hook;
27 | import com.discord.utilities.color.ColorCompat;
28 | import com.discord.widgets.chat.list.actions.WidgetChatListActions;
29 | import com.lytefast.flexinput.R;
30 |
31 | import java.util.regex.Pattern;
32 |
33 | @SuppressLint("SetTextI18n")
34 | @AliucordPlugin
35 | public class PluginDownloader extends Plugin {
36 | private final int id = View.generateViewId();
37 | private final Pattern repoPattern = Pattern.compile("https?://github\\.com/([A-Za-z0-9\\-_.]+)/([A-Za-z0-9\\-_.]+)");
38 | private final Pattern zipPattern = Pattern.compile("https?://(?:github|raw\\.githubusercontent)\\.com/([A-Za-z0-9\\-_.]+)/([A-Za-z0-9\\-_.]+)/(?:raw|blob)?/?\\w+/(\\w+).zip");
39 |
40 | @Override
41 | public void start(Context context) {
42 | patcher.patch(WidgetChatListActions.class, "configureUI", new Class>[] {WidgetChatListActions.Model.class} , new Hook(param -> {
43 | var _this = (WidgetChatListActions) param.thisObject;
44 | var rootView = (NestedScrollView) _this.requireView();
45 | var layout = (LinearLayout) rootView.getChildAt(0);
46 | if (layout == null || layout.findViewById(id) != null) return;
47 | var ctx = layout.getContext();
48 | var msg = ((WidgetChatListActions.Model) param.args[0]).getMessage();
49 | if (msg == null || msg.getContent() == null) return;
50 | String content = msg.getContent();
51 | long channelId = msg.getChannelId();
52 |
53 | if (channelId == Constants.PLUGIN_LINKS_UPDATES_CHANNEL_ID || channelId == Constants.PLUGIN_SUPPORT_CHANNEL_ID) {
54 | var matcher = zipPattern.matcher(content);
55 | while (matcher.find()) {
56 | String author = matcher.group(1);
57 | String repo = matcher.group(2);
58 | String name = matcher.group(3);
59 | var view = new TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon);
60 | view.setId(id);
61 | view.setText("Download " + name);
62 | var icon = ContextCompat.getDrawable(ctx, R.e.ic_file_download_white_24dp);
63 | if (icon != null) {
64 | icon = icon.mutate();
65 | icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal));
66 | view.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
67 | }
68 | view.setOnClickListener(e -> PDUtil.downloadPlugin(e.getContext(), author, repo, name, _this::dismiss));
69 | layout.addView(view, 1);
70 | }
71 | } else if (channelId == Constants.PLUGIN_LINKS_CHANNEL_ID) {
72 | var repoMatcher = repoPattern.matcher(content);
73 | if (!repoMatcher.find()) return; // zzzzzzz don't post junk
74 | String author = repoMatcher.group(1);
75 | String repo = repoMatcher.group(2);
76 |
77 | var modal = new Modal(author, repo);
78 | var view = new TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon);
79 | view.setId(id);
80 | view.setText("Open Plugin Downloader");
81 | var icon = ContextCompat.getDrawable(ctx, R.e.ic_file_download_white_24dp);
82 | if (icon != null) {
83 | icon = icon.mutate();
84 | icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal));
85 | view.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
86 | }
87 | view.setOnClickListener(e -> {
88 | Utils.openPageWithProxy(e.getContext(), modal);
89 | _this.dismiss();
90 | });
91 | layout.addView(view, 1);
92 | }
93 | }));
94 | }
95 |
96 | @Override
97 | public void stop(Context context) {
98 | patcher.unpatchAll();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vee's Aliucord Plugins
2 |
3 | > **warning**
4 | > i no longer work on my aliucord plugins. my plugins are considered finished. i will only merge prs that fix issues
5 |
6 | Click a Plugin's name to open a more detailed explanation
7 |
8 | - ### [Themer](Themer/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/Themer.zip?raw=true)
9 | Create and apply custom themes
10 |
11 | - ### [EmojiUtility](EmojiUtility/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/EmojiUtility.zip?raw=true)
12 | Adds lots of emoji utility, such as cloning and downloading
13 |
14 | - ### [MessageLinkEmbeds](MessageLinkEmbeds/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/MessageLinkEmbeds.zip?raw=true)
15 | Embeds message links
16 |
17 | - ### [PluginDownloader](PluginDownloader/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/PluginDownloader.zip?raw=true)
18 | Download and install plugins without ever leaving the Discord app
19 |
20 | - ### [BetterSpotify](BetterSpotify/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/BetterSpotify.zip?raw=true)
21 | Better Spotify integration (listen along)
22 |
23 | - ### [ViewProfileImages](ViewProfileImages/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/ViewProfileImages.zip?raw=true)
24 | Allows you to open user or server avatars/banners by tapping them, similar to attachments or embeds.
25 |
26 | - ### [DedicatedPluginSettings](DedicatedPluginSettings/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/DedicatedPluginSettings.zip?raw=true)
27 | Dedicated plugin settings on the main settings screen, similar to Powercord
28 |
29 | - ### [TapTap](TapTap/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/TapTap.zip?raw=true)
30 | Double-tap someone elses message to quick reply, double tap your own to quick edit
31 |
32 | - ### [AnimateApngs](AnimateApngs/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/AnimateApngs.zip?raw=true)
33 | Properly animate apngs
34 |
35 | - ### [CheckLinks](CheckLinks/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/CheckLinks.zip?raw=true)
36 | Simple plugin that checks links using the VirusTotal api and shows the result in the "Are you sure you want to open this link" modal
37 |
38 | - ### [UrbanDictionary](UrbanDictionary/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/UrbanDictionary.zip?raw=true)
39 | Query the Urban Dictionary for definitions
40 |
41 | - ### [Hastebin](Hastebin/README.md) - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/Hastebin.zip?raw=true)
42 | Create pastes on your favourite Hastebin Mirror
43 |
--------------------------------------------------------------------------------
/ShowBlockedMessages/README.md:
--------------------------------------------------------------------------------
1 | # ShowBlockedMessages
2 |
3 | Makes it so that messages of blocked users show up just like regular messages (without the 1 blocked message stuff).
4 |
5 | Probably not too useful to most people, I just made it so that i can block people who dm me annoying stuff / ping spam while still
6 | being able to talk to them on servers.
7 |
--------------------------------------------------------------------------------
/ShowBlockedMessages/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.0"
2 | description = "Does not hide messages of blocked users behind the Blocked Message Chat Entry. You may need to restart after blocking someone"
3 |
--------------------------------------------------------------------------------
/ShowBlockedMessages/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ShowBlockedMessages/src/main/kotlin/dev/vendicated/aliucordplugins/showblockedmessages/ShowBlockedMessages.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.showblockedmessages
12 |
13 | import android.content.Context
14 | import com.aliucord.annotations.AliucordPlugin
15 | import com.aliucord.entities.Plugin
16 | import com.aliucord.patcher.Hook
17 | import com.discord.stores.StoreUserRelationships
18 |
19 | fun Map.mutable() = if (this is HashMap) this else HashMap(this)
20 |
21 | // Evil Alyxia be like
22 | @AliucordPlugin
23 | class ShowBlockedMessages : Plugin() {
24 | override fun start(ctx: Context) {
25 | patcher.patch(StoreUserRelationships::class.java.getDeclaredMethod("getRelationships"), Hook {
26 | (it.result as Map).mutable().run {
27 | forEach { (k, v) ->
28 | if (v == 2) put(k, 0)
29 | }
30 | it.result = this
31 | }
32 | })
33 | }
34 |
35 | override fun stop(context: Context) {
36 | patcher.unpatchAll()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TapTap/README.md:
--------------------------------------------------------------------------------
1 | # TapTap - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/TapTap.zip?raw=true)
2 |
3 | Double-tap someone elses message to quick reply, double tap your own to quick edit
4 |
--------------------------------------------------------------------------------
/TapTap/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.1.6"
2 | description = "Double tap someone else's message to quick reply, double tap your own to quick edit"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 1.1.6
7 | * Ignore messages you cannot reply to
8 |
9 | # 1.1.4
10 | * Optionally reply to own messages instead of editing them
11 |
12 | # 1.1.3
13 | * Select the input box if opening keyboard
14 |
15 | # 1.1.0
16 | * Automatically open keyboard when replying to a message. Disabled by default, toggleable via settings
17 | """.trimIndent()
18 | )
19 |
20 | aliucord.author("Diamond", 295190422244950017L)
21 |
--------------------------------------------------------------------------------
/TapTap/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/TapTap/src/main/java/dev/vendicated/aliucordplugs/taptap/TapTap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.taptap;
12 |
13 | import android.content.Context;
14 | import android.os.Handler;
15 | import android.util.AttributeSet;
16 | import android.view.View;
17 | import android.view.inputmethod.InputMethodManager;
18 | import android.widget.LinearLayout;
19 |
20 | import androidx.core.widget.NestedScrollView;
21 |
22 | import com.aliucord.Utils;
23 | import com.aliucord.annotations.AliucordPlugin;
24 | import com.aliucord.entities.Plugin;
25 | import com.aliucord.patcher.Hook;
26 | import com.aliucord.patcher.InsteadHook;
27 | import com.discord.models.message.Message;
28 | import com.discord.models.user.CoreUser;
29 | import com.discord.stores.StoreStream;
30 | import com.discord.widgets.chat.list.actions.WidgetChatListActions;
31 | import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterEventsHandler;
32 | import com.lytefast.flexinput.widget.FlexEditText;
33 |
34 | import java.util.concurrent.atomic.AtomicBoolean;
35 |
36 | @AliucordPlugin
37 | public class TapTap extends Plugin {
38 | public static final int defaultDelay = 200;
39 | private Runnable unpatch;
40 | public Runnable togglePatch;
41 |
42 | public TapTap() {
43 | settingsTab = new SettingsTab(TapTapSettings.class).withArgs(this);
44 | }
45 |
46 | private WidgetChatListActions widgetChatListActions;
47 | private FlexEditText flexInput;
48 | private static final Handler handler = new Handler();
49 | private final AtomicBoolean busy = new AtomicBoolean(false);
50 | private int clicks = 0;
51 |
52 | @Override
53 | public void start(Context ctx) throws Throwable {
54 | final int editId = Utils.getResId("dialog_chat_actions_edit", "id");
55 | final int replyId = Utils.getResId("dialog_chat_actions_reply", "id");
56 |
57 | Utils.mainThread.post(() -> widgetChatListActions = new WidgetChatListActions());
58 |
59 | patcher.patch(FlexEditText.class.getDeclaredConstructor(Context.class, AttributeSet.class), new Hook(param -> flexInput = (FlexEditText) param.thisObject));
60 |
61 | patcher.patch(WidgetChatListAdapterEventsHandler.class.getDeclaredMethod("onMessageClicked", Message.class, boolean.class), new InsteadHook(param -> {
62 | var msg = (Message) param.args[0];
63 | if (msg.isEphemeralMessage() || msg.isLocal() || msg.isFailed() || msg.isLoading())
64 | return null;
65 |
66 | if (busy.getAndSet(true)) {
67 | return null;
68 | }
69 |
70 | clicks++;
71 |
72 | handler.postDelayed(() -> {
73 | if (clicks >= 2) {
74 | if (isMe(msg) && !settings.getBool("replyToOwn", false)) {
75 | WidgetChatListActions.access$editMessage(widgetChatListActions, msg);
76 | } else {
77 | WidgetChatListActions.access$replyMessage(widgetChatListActions, msg, StoreStream.getChannels().getChannel(msg.getChannelId()));
78 | if (settings.getBool("openKeyboard", false)) {
79 | var imm = (InputMethodManager) Utils.getAppContext().getSystemService(Context.INPUT_METHOD_SERVICE);
80 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
81 | if (flexInput != null) flexInput.requestFocus();
82 | }
83 | }
84 | } else {
85 | if ((boolean) param.args[1])
86 | StoreStream.Companion.getMessagesLoader().jumpToMessage(msg.getChannelId(), msg.getId());
87 | }
88 | clicks = 0;
89 | }, settings.getInt("doubleTapWindow", defaultDelay));
90 |
91 | busy.set(false);
92 | return null;
93 | }));
94 |
95 | togglePatch = () -> {
96 | if (settings.getBool("hideButtons", false)) {
97 | if (unpatch == null)
98 | unpatch = patcher.patch(WidgetChatListActions.class, "configureUI", new Class>[]{WidgetChatListActions.Model.class}, new Hook(param -> {
99 | var _this = (WidgetChatListActions) param.thisObject;
100 | var rootView = (NestedScrollView) _this.requireView();
101 | var layout = (LinearLayout) rootView.getChildAt(0);
102 | var msg = ((WidgetChatListActions.Model) param.args[0]).getMessage();
103 | if (isMe(msg)) {
104 | layout.findViewById(editId).setVisibility(View.GONE);
105 | } else {
106 | layout.findViewById(replyId).setVisibility(View.GONE);
107 | }
108 | }));
109 | } else {
110 | if (unpatch != null) unpatch.run();
111 | unpatch = null;
112 | }
113 | };
114 | togglePatch.run();
115 | }
116 |
117 | private static boolean isMe(Message msg) {
118 | long authorId = new CoreUser(msg.getAuthor()).getId();
119 | long myId = StoreStream.getUsers().getMe().getId();
120 | return authorId == myId;
121 | }
122 |
123 | @Override
124 | public void stop(Context ctx) {
125 | patcher.unpatchAll();
126 | flexInput = null;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/TapTap/src/main/java/dev/vendicated/aliucordplugs/taptap/TapTapSettings.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.taptap
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.text.Editable
16 | import android.text.InputType
17 | import android.view.View
18 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
19 | import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
20 | import android.widget.LinearLayout
21 | import com.aliucord.Utils
22 | import com.aliucord.fragments.SettingsPage
23 | import com.aliucord.utils.DimenUtils
24 | import com.aliucord.views.TextInput
25 | import com.discord.utilities.color.ColorCompat
26 | import com.discord.utilities.view.text.TextWatcher
27 | import com.discord.views.CheckedSetting
28 | import com.google.android.material.card.MaterialCardView
29 | import com.lytefast.flexinput.R
30 |
31 | class TapTapSettings(private val plugin: TapTap) : SettingsPage() {
32 | @SuppressLint("SetTextI18n")
33 | override fun onViewBound(view: View) {
34 | super.onViewBound(view)
35 |
36 | setActionBarTitle("TapTap")
37 |
38 | val ctx = view.context
39 |
40 | TextInput(ctx, "Double Tap Window (in ms)").run {
41 | editText.run {
42 | maxLines = 1
43 | setText(plugin.settings.getInt("doubleTapWindow", TapTap.defaultDelay).toString())
44 | inputType = InputType.TYPE_CLASS_NUMBER
45 |
46 | addTextChangedListener(object : TextWatcher() {
47 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
48 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
49 | override fun afterTextChanged(s: Editable) {
50 | val i = try {
51 | s.toString().toInt()
52 | } catch (th: NumberFormatException) {
53 | TapTap.defaultDelay
54 | }
55 | plugin.settings.setInt("doubleTapWindow", i)
56 | }
57 | })
58 | }
59 |
60 | linearLayout.addView(this)
61 | }
62 |
63 | addCheckedSetting(
64 | ctx,
65 | "Hide Buttons",
66 | "Hides reply & edit buttons in the message actions menu. The reply button will still be shown on your own messages.",
67 | "hideButtons"
68 | )
69 |
70 | addCheckedSetting(
71 | ctx,
72 | "Automatically open keyboard",
73 | "Automatically opens the keyboard when replying to a message",
74 | "openKeyboard"
75 | )
76 |
77 | addCheckedSetting(
78 | ctx,
79 | "Reply to own messages",
80 | "Reply to your own messages instead of editing them.",
81 | "replyToOwn"
82 | )
83 | }
84 |
85 | private fun addCheckedSetting(ctx: Context, text: String, subtext: String, key: String) =
86 | MaterialCardView(ctx).apply {
87 | radius = DimenUtils.defaultCardRadius.toFloat()
88 | setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundSecondary))
89 |
90 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
91 | setMargins(0, DimenUtils.defaultPadding, 0, 0)
92 | }
93 |
94 | Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, text, subtext).let {
95 | it.isChecked = plugin.settings.getBool(key, false)
96 | it.setOnCheckedListener { checked ->
97 | plugin.settings.setBool(key, checked)
98 | plugin.togglePatch.run()
99 | }
100 | addView(it)
101 | }
102 |
103 | linearLayout.addView(this)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Template/README.md:
--------------------------------------------------------------------------------
1 | # Template - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/DeezNuts.zip?raw=true)
2 |
3 | This is the Template I generate all my plugins from. Not much to see here :)
4 |
--------------------------------------------------------------------------------
/Template/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.0"
2 | description = "Punch 6pak for making me update 15 plugins"
3 |
--------------------------------------------------------------------------------
/Template/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Template/src/main/kotlin/dev/vendicated/aliucordplugins/template/Template.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.template
12 |
13 | import android.content.Context
14 | import com.aliucord.annotations.AliucordPlugin
15 | import com.aliucord.entities.Plugin
16 |
17 | @AliucordPlugin
18 | class Template : Plugin() {
19 | override fun start(ctx: Context) {
20 |
21 | }
22 |
23 | override fun stop(context: Context) {
24 | patcher.unpatchAll()
25 | commands.unregisterAll()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TextFilePreview/README.md:
--------------------------------------------------------------------------------
1 | # TextFilePreview - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/TextFilePreview.zip?raw=true)
2 |
3 | Adds previews to text file embeds.
4 |
5 | Only a small preview (300 bytes by default) is downloaded, so the data usage will not be much higher
6 |
7 | 
8 |
--------------------------------------------------------------------------------
/TextFilePreview/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.3"
2 | description = "Preview the content of Text files in Discord"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 1.0.3
7 | * Fix probably idk i didnt test
8 |
9 | # 1.0.1
10 | * Reduce default preview size to 300 and add a setting for it
11 | * Add close button that will hide the preview
12 | * Add copy button that will copy the file content to your clipboard
13 |
14 | """.trimIndent()
15 | )
16 |
--------------------------------------------------------------------------------
/TextFilePreview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/TextFilePreview/src/main/kotlin/dev/vendicated/aliucordplugins/textfilepreview/LeSettings.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugins.textfilepreview
12 |
13 | import android.text.Editable
14 | import android.text.InputType
15 | import android.view.View
16 | import com.aliucord.PluginManager
17 | import com.aliucord.fragments.SettingsPage
18 | import com.aliucord.settings.delegate
19 | import com.aliucord.views.TextInput
20 | import com.discord.utilities.view.text.TextWatcher
21 |
22 | class LeSettings() : SettingsPage() {
23 | private var previewSize: Int by PluginManager.plugins["TextFilePreview"]!!.settings.delegate(300)
24 |
25 | override fun onViewBound(view: View) {
26 | super.onViewBound(view)
27 |
28 | setActionBarTitle("TextFilePreview")
29 |
30 | TextInput(view.context, "Preview Size (in characters)").run {
31 | editText.run {
32 | maxLines = 1
33 | setText(previewSize.toString())
34 | inputType = InputType.TYPE_CLASS_NUMBER
35 |
36 | addTextChangedListener(object : TextWatcher() {
37 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
38 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
39 | override fun afterTextChanged(s: Editable) {
40 | previewSize = try {
41 | s.toString().toInt()
42 | } catch (th: NumberFormatException) {
43 | 300
44 | }
45 | }
46 | })
47 | }
48 |
49 | linearLayout.addView(this)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Themer/Firefly.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest": {
3 | "author": "Ven#8810",
4 | "version": "1.0.1",
5 | "name": "Firefly",
6 | "updater": "https:\/\/raw.githubusercontent.com\/Vendicated\/AliucordPlugins\/main\/Themer\/Firefly.json"
7 | },
8 | "background": {
9 | "url": "https:\/\/media.discordapp.net\/attachments\/811263527239024640\/887828992164511764\/797185.png",
10 | "overlay_alpha": 0,
11 | "blur_radius": 0
12 | },
13 | "fonts": {},
14 | "simple_colors": {
15 | "background": 1224736768,
16 | "background_secondary": 603979776,
17 | "accent": -2058867,
18 | "mention_highlight": 669029773
19 | },
20 | "colors": {},
21 | "drawable_tints": {}
22 | }
--------------------------------------------------------------------------------
/Themer/README.md:
--------------------------------------------------------------------------------
1 | # Themer - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/Themer.zip?raw=true)
2 |
3 | Custom Themes!
4 |
5 | Note: This plugin is quite heavy. If your Aliucord becomes slow, it may be why. If you are rooted, consider using
6 | the [Xposed Discord Themer](https://github.com/Aliucord/DiscordThemer) instead.
7 |
8 | ### Features
9 | - Custom colours
10 | - Custom icon (drawable) colours
11 | - Optional transparency with a custom background wallpaper (may be laggy on older devices)
12 | - Custom fonts
13 | - Themes are single json files
14 | - Theme auto updates
15 | - Fully featured Theme Editor
16 |
17 | Simply install the plugin and install a theme from the #themes channel on the Aliucord server by long pressing the message and clicking the context menu install button.
18 |
19 | ### Alternatively, create your own theme
20 |
21 | Simply open the plugin settings, click new theme and go crazy! Check the pins in the #theme-development for help.
22 |
23 | ### Example Theme
24 |
25 | ⚠️ Requires you to enable "Full Transparency" in the plugin settings
26 |
27 | [Download](https://cdn.discordapp.com/attachments/852332951542956052/887844412745482310/Firefly.json) | [Source](https://raw.githubusercontent.com/Vendicated/AliucordPlugins/main/Themer/Firefly.json)
28 |
29 | 
30 | 
31 | 
32 | 
33 |
34 | ### Theme spec (SUBJECT TO CHANGE)
35 | ```json
36 | {
37 | "manifest": {
38 | "name": "Awesome Theme",
39 | "version": "1.0.0",
40 | "author": "Ven",
41 | "license": "Unlicense",
42 | "updater": "url to raw json file, will be used to update your theme"
43 | },
44 | "simple_colors": {
45 | "some_color_name": -1371931
46 | },
47 | "background": {
48 | "url": "background-url",
49 | "overlay_alpha": 150,
50 | "blur_radius": 1.7
51 | },
52 | "fonts": {
53 | "*": "everywhere-font-url",
54 | "ginto_bold": "ginto-replacement-url"
55 | },
56 | "colors": {
57 | "brand_new": -13978120
58 | },
59 | "drawableTints": {
60 | "drawable_overlay_channels_selected_dark": -131020192
61 | }
62 | }
63 | ```
64 |
--------------------------------------------------------------------------------
/Themer/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "3.6.2"
2 | description = "Apply custom themes to your Discord"
3 |
4 | aliucord.changelog.set(
5 | """
6 | # 3.6.2
7 | * Now prompts to switch to dark mode if using light/pureEvil theme
8 |
9 | # 3.6.1
10 | * Re-enabled custom fonts. They may still be unstable, so use at your own risk
11 | """.trimIndent()
12 | )
13 |
--------------------------------------------------------------------------------
/Themer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/Constants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import com.aliucord.Constants
14 | import java.io.File
15 | import java.util.regex.Pattern
16 |
17 | enum class TransparencyMode(val value: Int) {
18 | NONE(0),
19 | CHAT(1),
20 | CHAT_SETTINGS(2),
21 | FULL(3);
22 |
23 | companion object {
24 | fun from(i: Int) = values().first { it.value == i }
25 | }
26 | }
27 |
28 | const val DEFAULT_OVERLAY_ALPHA = 150
29 | val THEME_DIR = File(Constants.BASE_PATH, "themes")
30 |
31 | // Discord has these HARDCODED instead of using colour resource :husk:
32 | // Thus, compare colour in ColorDrawable.setColor to these values to replace them
33 | const val BLOCKED_COLOR_DARK = 0xff34373c.toInt()
34 | const val BLOCKED_COLOR_LIGHT = 0xfffcfcfc.toInt()
35 |
36 | // Credit for these colours to both https://github.com/Aliucord/DiscordThemer
37 | // and https://github.com/GangsterFox/AliuFox-themes/blob/main/ThemerDocu.md
38 |
39 | val ALLOWED_RESOURCE_DOMAINS = arrayOf(
40 | "github.com",
41 | "raw.githubusercontent.com",
42 | "gitlab.com",
43 | "cdn.discordapp.com",
44 | "media.discordapp.net",
45 | "i.imgur.com",
46 | "i.ibb.co", // only for you, FrozenPhoton
47 | )
48 |
49 | val ALLOWED_RESOURCE_DOMAINS_PATTERN: Pattern by lazy {
50 | val domains = ALLOWED_RESOURCE_DOMAINS.joinToString(
51 | separator = "|",
52 | transform = { Pattern.quote(it) }
53 | )
54 | Pattern.compile("^https://(www)?($domains)/")
55 | }
56 |
57 | val THEME_KEYS = arrayOf(
58 | "manifest",
59 | "background",
60 | "fonts",
61 | "raws",
62 | "simple_colors",
63 | "colors",
64 | "drawable_tints"
65 | )
66 |
67 | val SIMPLE_KEYS = arrayOf(
68 | "accent",
69 | "background",
70 | "background_secondary",
71 | "mention_highlight",
72 | "active_channel",
73 | "statusbar",
74 | "input_background",
75 | "blocked_bg"
76 | )
77 |
78 | val SIMPLE_ACCENT_NAMES = arrayOf(
79 | "link",
80 | "link_light",
81 | "brand_new",
82 | "brand_new_230",
83 | "brand_new_360", // cursor
84 | "brand_new_500",
85 | "brand_new_530",
86 | "brand_new_560", // reactions
87 | "brand_new_600",
88 | // =========== Buttons ============
89 | "uikit_btn_bg_color_selector_brand",
90 | "uikit_btn_bg_color_selector_secondary_dark",
91 | "uikit_btn_compound_color_selector_dark",
92 | "uikit_btn_compound_color_selector_light",
93 | )
94 |
95 | val SIMPLE_BG_NAMES = arrayOf(
96 | "dark_grey_2",
97 | "primary_600",
98 | "primary_660",
99 | "primary_800",
100 | "primary_dark_600",
101 | "primary_dark_630",
102 | "primary_dark_800"
103 | )
104 |
105 | val SIMPLE_BG_SECONDARY_NAMES = arrayOf(
106 | "primary_500",
107 | "primary_630",
108 | "primary_700",
109 | "primary_dark_660",
110 | "primary_dark_700",
111 | "input_background",
112 | "statusbar",
113 | "active_channel",
114 | )
115 |
116 |
117 | val SIMPLE_ACCENT_ATTRS = arrayOf(
118 | "color_brand",
119 | "brand_new_500",
120 | "colorControlBrandForeground",
121 | "colorControlActivated",
122 | "colorTextLink",
123 | "__alpha_10_theme_chat_mention_background",
124 | "theme_chat_mention_foreground"
125 | )
126 |
127 | val SIMPLE_BG_ATTRS = arrayOf(
128 | "colorSurface",
129 | "colorBackgroundFloating",
130 | "colorTabsBackground",
131 | "theme_chat_spoiler_inapp_bg",
132 | "primary_600",
133 | "primary_660",
134 | "primary_800"
135 | )
136 |
137 | val SIMPLE_BG_SECONDARY_ATTRS = arrayOf(
138 | "colorBackgroundTertiary",
139 | "colorBackgroundSecondary",
140 | "primary_700",
141 | "theme_chat_spoiler_bg",
142 | )
143 |
144 | val SIMPLE_SOUND_NAMES = arrayOf(
145 | "call_calling",
146 | "call_ringing",
147 | "deafen",
148 | "mute",
149 | "undeafen",
150 | "unmute",
151 | "reconnect",
152 | "stream_ended",
153 | "stream_started",
154 | "stream_user_joined",
155 | "stream_user_left",
156 | "user_join",
157 | "user_leave",
158 | "user_moved"
159 | )
160 |
161 | inline fun pairOf(v: T) = v to v
162 | inline fun pairOf(a: T, b: T) = Pair(a, b)
163 |
164 | val ATTR_MAPPINGS = HashMap>()
165 |
166 | fun initAttrMappings() {
167 | ATTR_MAPPINGS.clear()
168 | val map = mapOf(
169 | pairOf("brand_new") to arrayOf("color_brand"),
170 | pairOf("brand_new_360", "brand_new_500") to arrayOf("colorControlBrandForeground"),
171 | pairOf("brand_new_260", "brand_new_530") to arrayOf("theme_chat_mention_foreground"),
172 | pairOf("brand_new_500") to arrayOf("color_brand_500"),
173 | pairOf("brand_360", "brand_500") to arrayOf("colorControlActivated"),
174 | pairOf("brand_500_alpha_20", "brand_new_160") to arrayOf("theme_chat_mention_background"),
175 | pairOf("link", "link_light") to arrayOf("colorTextLink"),
176 | pairOf("mention_highlight") to arrayOf("theme_chat_mentioned_me"),
177 |
178 | pairOf("primary_dark_600", "white") to arrayOf("colorBackgroundPrimary"),
179 | pairOf("primary_dark_800", "white") to arrayOf(
180 | "colorSurface",
181 | "colorBackgroundFloating",
182 | "colorTabsBackground"
183 | ),
184 | pairOf("primary_600", "primary_300") to arrayOf("primary_600", "theme_chat_spoiler_inapp_bg"),
185 | pairOf("primary_630", "white_1") to arrayOf("theme_chat_code"),
186 | pairOf("primary_660", "white_2") to arrayOf("primary_660", "theme_chat_codeblock_border"),
187 | pairOf("primary_700", "primary_light_200") to arrayOf(
188 | "primary_700",
189 | "colorBackgroundTertiary",
190 | "colorBackgroundSecondary",
191 | ),
192 | pairOf("primary_800", "primary_200") to arrayOf("primary_800"),
193 | pairOf("primary_900", "primary_100") to arrayOf("primary_900"),
194 | pairOf("primary_300", "primary_700") to arrayOf("theme_chat_spoiler_bg"),
195 | pairOf("black_alpha_10", "white_alpha_10") to arrayOf("theme_chat_spoiler_bg_visible")
196 | )
197 |
198 | with(ATTR_MAPPINGS) {
199 | val isLightMode = currentTheme == "light"
200 | map.forEach { (k, v) ->
201 | val key = if (isLightMode) k.second else k.first
202 | if (containsKey(key)) {
203 | put(key, arrayOf(*get(key)!!, *v))
204 | } else put(key, v)
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/Legacy.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import org.json.JSONObject
14 |
15 | fun convertLegacyTheme(theme: Theme, json: JSONObject) {
16 | logger.info("Converting legacy theme ${theme.name} to new format...")
17 |
18 | val manifest = JSONObject()
19 | val background = JSONObject()
20 | val fonts = JSONObject()
21 | val simpleColors = JSONObject()
22 | val colors = JSONObject()
23 | val drawableTints = JSONObject()
24 |
25 | for (key in json.keys().asSequence().sorted()) {
26 | when (key) {
27 | "author", "version", "name", "license", "updater" -> manifest.puts(json, key)
28 |
29 | "background_url" -> background.puts(json, key, "url")
30 | "background_transparency" -> background.putInt(json, "background_transparency", "overlay_alpha")
31 |
32 | "font" -> fonts.puts(json, key, "*")
33 |
34 | "simple_accent_color" -> simpleColors.putInt(json, key, "accent")
35 | "simple_bg_color" -> simpleColors.putInt(json, key, "background")
36 | "simple_bg_secondary_color" -> simpleColors.putInt(json, key, "background_secondary")
37 | "mention_highlight" -> simpleColors.putInt(json, key)
38 | "active_channel_color", "statusbar_color", "input_background_color" -> simpleColors.putInt(json, key, key.removeSuffix("_color"))
39 |
40 | else -> {
41 | when {
42 | key.startsWith("color_") -> colors.putInt(json, key, key.substring("color_".length))
43 | key.startsWith("drawablecolor_") -> drawableTints.putInt(json, key, key.substring("drawablecolor_".length))
44 | key.startsWith("font_") -> fonts.puts(json, key, key.substring("font_".length))
45 | else -> logger.warn("[${theme.name}] Unrecognised key $key")
46 | }
47 | }
48 | }
49 | }
50 |
51 | JSONObject().run {
52 | put("manifest", manifest)
53 | put("background", background)
54 | put("fonts", fonts)
55 | put("simple_colors", simpleColors)
56 | put("colors", colors)
57 | put("drawable_tints", drawableTints)
58 |
59 | theme.file.writeText(toString(4))
60 |
61 | logger.info("Finished converting theme ${theme.name}!")
62 | }
63 | }
64 |
65 | private fun JSONObject.puts(json: JSONObject, key: String, newKey: String = key) = put(newKey, json.getString(key))
66 | private fun JSONObject.putInt(json: JSONObject, key: String, newKey: String = key) = put(newKey, json.getInt(key))
67 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/ResourceManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import android.content.Context
14 | import android.graphics.Typeface
15 | import android.graphics.drawable.BitmapDrawable
16 | import android.net.Uri
17 | import androidx.core.graphics.ColorUtils
18 | import com.aliucord.Utils
19 | import com.lytefast.flexinput.R
20 | import java.io.File
21 |
22 | private var colorToName = HashMap()
23 |
24 | private val fonts = HashMap()
25 | private val colorsByName = HashMap()
26 | private val colorsById = HashMap()
27 | private val drawableTints = HashMap()
28 | private val attrs = HashMap()
29 | private val raws = HashMap()
30 |
31 | object ResourceManager {
32 | var customBg = null as BitmapDrawable?
33 | var animatedBgUri = null as Uri?
34 | var overlayAlpha = 0
35 |
36 | fun getColorReplacement(color: Int) = getNameByColor(color)?.let {
37 | getColorForName(it)
38 | }
39 |
40 | fun getNameByColor(color: Int) = colorToName[color]
41 | fun getColorForName(name: String) = colorsByName[name]
42 | fun getColorForId(id: Int) = colorsById[id]
43 | fun getDrawableTintForId(id: Int) = drawableTints[id]
44 | fun getAttrForId(id: Int) = attrs[id]
45 | fun getFontForId(id: Int) = fonts[id]
46 | fun getRawForId(id: Int) = raws[id]
47 | fun getDefaultFont() = getFontForId(-1)
48 |
49 | fun init(ctx: Context) {
50 | R.c::class.java.declaredFields.forEach {
51 | val color = ctx.getColor(it.getInt(null))
52 | if (color != 0) colorToName[color] = it.name
53 | }
54 | }
55 |
56 | fun clean() {
57 | fonts.clear()
58 | colorsByName.clear()
59 | colorsById.clear()
60 | drawableTints.clear()
61 | attrs.clear()
62 | raws.clear()
63 | customBg = null
64 | animatedBgUri = null
65 | }
66 |
67 | internal fun putFont(id: Int, font: Typeface) {
68 | fonts[id] = font
69 | }
70 |
71 | internal fun putRaw(name: String, file: File) {
72 | val id = Utils.getResId(name, "raw")
73 | if (id != 0)
74 | raws[id] = file
75 | else
76 | logger.warn("Unrecognised raw $name")
77 | }
78 |
79 | internal fun putColor(name: String, color: Int) {
80 | val id = Utils.getResId(name, "color")
81 | if (id != 0) {
82 | colorsById[id] = color
83 | colorsByName[name] = color
84 | } else {
85 | when (name) {
86 | "statusbar", "input_background", "active_channel", "blocked_bg" -> colorsByName[name] = color
87 | else -> logger.warn("Unrecognised colour $name")
88 | }
89 | }
90 | }
91 |
92 | internal fun putColors(names: Array, color: Int) = names.forEach {
93 | putColor(it, color)
94 | }
95 |
96 | internal fun putDrawableTint(name: String, color: Int) {
97 | val id = Utils.getResId(name, "drawable")
98 | if (id != 0)
99 | drawableTints[id] = color
100 | else
101 | logger.warn("Unrecognised drawable $name")
102 | }
103 |
104 | internal fun putAttr(name: String?, color: Int) =
105 | ATTR_MAPPINGS[name]?.forEach {
106 | setAttr(it, color)
107 | }
108 |
109 | internal fun putAttrs(attrs: Array, color: Int) {
110 | attrs.forEach {
111 | if (it.startsWith("__alpha_10_")) {
112 | val prefixLen = 11
113 | setAttr(it.substring(prefixLen), ColorUtils.setAlphaComponent(color, 0x1a))
114 | } else setAttr(it, color)
115 | }
116 | }
117 |
118 | private fun setAttr(attr: String, color: Int) {
119 | val id = Utils.getResId(attr, "attr")
120 | if (id == 0) logger.warn("No such attribute: $attr") else attrs[id] = color
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import com.aliucord.Http
14 | import com.aliucord.Utils
15 | import com.aliucord.updater.Updater
16 | import com.discord.stores.StoreStream
17 | import com.discord.utilities.user.UserUtils
18 | import org.json.JSONObject
19 | import java.io.File
20 | import java.io.IOException
21 |
22 | class ThemeException(override val message: String) : IOException(message)
23 |
24 | class Theme(
25 | val file: File
26 | ) {
27 | var name: String
28 | var author: String = "Anonymous"
29 | var version: String = "1.0.0"
30 | var license: String? = null
31 | private var updaterUrl: String? = null
32 |
33 | init {
34 | name = file.name.removeSuffix(".json")
35 | val json = json()
36 | (json.optJSONObject("manifest") ?: json /* legacy format */).run {
37 | if (has("name")) name = getString("name")
38 | if (has("author")) author = getString("author")
39 | if (has("version")) version = getString("version")
40 | if (has("license")) license = getString("license")
41 | if (has("updater")) updaterUrl = getString("updater")
42 | }
43 | }
44 |
45 | fun json() = JSONObject(file.readText())
46 |
47 | private val prefsKey
48 | get() = "$name-enabled"
49 |
50 | var isEnabled
51 | get() = Themer.mSettings.getBool(prefsKey, false)
52 | set(v) = Themer.mSettings.setBool(prefsKey, v)
53 |
54 | fun convertIfLegacy(): Boolean {
55 | val json = json()
56 |
57 | val isLegacy = THEME_KEYS.none { json.has(it) }
58 | if (isLegacy) convertLegacyTheme(this@Theme, json)
59 | return isLegacy
60 | }
61 |
62 | fun update() =
63 | updaterUrl?.let {
64 | Utils.threadPool.execute {
65 | try {
66 | verifyUntrustedUrl(it)
67 | Http.Request(it).use { req ->
68 | val res = req.execute().text()
69 | val json = JSONObject(res)
70 | val remoteVersion = (json.optJSONObject("manifest") ?: json).optString("version")
71 | if (remoteVersion.isNotEmpty() && Updater.isOutdated("Theme $name", version, remoteVersion)) {
72 | file.writeText(res)
73 | info("Successfully updated: $version -> $remoteVersion")
74 | }
75 | }
76 | } catch (ex: Throwable) {
77 | logger.error("Failed to update theme $name", ex)
78 | }
79 | }
80 | }
81 |
82 | fun error(msg: String, throwable: Throwable? = null) {
83 | logger.error("[${name.uppercase()}] $msg", throwable)
84 | }
85 |
86 | fun info(msg: String) {
87 | logger.info("[${name.uppercase()}] $msg")
88 | }
89 |
90 | companion object {
91 | fun create(name: String): Theme {
92 | val file = File(THEME_DIR, "${name.trim()}.json")
93 | if (file.exists()) throw ThemeException("A Theme with this name already exists.")
94 | val json = JSONObject()
95 | .put("name", name)
96 | .put("version", "1.0.0")
97 | .put("author", StoreStream.getUsers().me.run {
98 | "$username${UserUtils.INSTANCE.padDiscriminator(discriminator)}"
99 | })
100 | try {
101 | file.writeText(json.toString(4))
102 | } catch (ex: Throwable) {
103 | throw ThemeException("Failed to create theme file. Make sure the name doesn't contain special characters!")
104 | }
105 | return Theme(file)
106 | }
107 | }
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/Themer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import android.content.Context
14 | import android.content.res.Resources
15 | import android.os.Looper
16 | import com.aliucord.*
17 | import com.aliucord.annotations.AliucordPlugin
18 | import com.aliucord.api.SettingsAPI
19 | import com.aliucord.entities.Plugin
20 | import com.aliucord.patcher.PreHook
21 | import com.aliucord.utils.RxUtils.subscribe
22 | import com.discord.stores.StoreStream
23 | import dev.vendicated.aliucordplugs.themer.settings.ThemerSettings
24 | import rx.Subscription
25 |
26 | val logger = Logger("Themer")
27 | var currentTheme = ""
28 |
29 | @AliucordPlugin
30 | class Themer : Plugin() {
31 | private var subscription: Subscription? = null
32 |
33 | init {
34 | settingsTab = SettingsTab(ThemerSettings::class.java)
35 | }
36 |
37 | override fun start(ctx: Context) {
38 | currentTheme = StoreStream.getUserSettingsSystem().theme
39 | subscription = StoreStream.getUserSettingsSystem().observeSettings(false).subscribe {
40 | if (currentTheme != theme) {
41 | currentTheme = theme
42 | initAttrMappings()
43 | }
44 | }
45 | initAttrMappings()
46 | mSettings = settings
47 | addPatches(patcher)
48 | ResourceManager.init(ctx)
49 | ThemeLoader.loadThemes(true)
50 |
51 | // fixme
52 | patcher.patch(com.aliucord.Main::class.java.getDeclaredMethod("crashHandler", Thread::class.java, Throwable::class.java), PreHook {
53 | // Ignore thread exceptions
54 | if (Looper.getMainLooper().thread != it.args[0]) return@PreHook
55 | val ex = it.args[1] as? Resources.NotFoundException ?: return@PreHook
56 | when (ex.stackTrace.firstOrNull()?.methodName) {
57 | // Crash caused by font hook
58 | "loadFont", "getFont" -> {
59 | settings.enableFontHook = false
60 | settings.fontHookCausedCrash = true
61 | }
62 | }
63 | })
64 | }
65 |
66 | override fun stop(context: Context) {
67 | subscription?.unsubscribe()
68 | patcher.unpatchAll()
69 | ResourceManager.clean()
70 | ThemeLoader.themes.clear()
71 | Utils.appActivity.recreate()
72 | }
73 |
74 | companion object {
75 | lateinit var mSettings: SettingsAPI
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/Util.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer
12 |
13 | import com.aliucord.api.SettingsAPI
14 | import dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color.ColorTuple
15 | import org.json.JSONObject
16 |
17 | var SettingsAPI.transparencyMode
18 | get() = TransparencyMode.from(getInt("transparencyMode", TransparencyMode.NONE.value))
19 | set(v) = setInt("transparencyMode", v.value)
20 |
21 | var SettingsAPI.enableFontHook
22 | get() = getBool("enableFontHook", false)
23 | set(v) = setBool("enableFontHook", v)
24 |
25 | var SettingsAPI.customSounds
26 | get() = getBool("customSounds", false)
27 | set(v) = setBool("customSounds", v)
28 |
29 | var SettingsAPI.fontHookCausedCrash
30 | get() = getBool("fontHookCausedCrash", false)
31 | set(v) = setBool("fontHookCausedCrash", v)
32 |
33 | fun JSONObject.toColorArray() = ArrayList().apply {
34 | keys().forEach {
35 | add(ColorTuple(it, getInt(it)))
36 | }
37 | sortBy { it.name }
38 | }
39 |
40 | fun verifyUntrustedUrl(url: String) {
41 | if (!ALLOWED_RESOURCE_DOMAINS_PATTERN.matcher(url).find())
42 | throw IllegalArgumentException(
43 | "URL $url is not allowed. Please use one of: >> ${ALLOWED_RESOURCE_DOMAINS.joinToString()} <<"
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/ThemeCard.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings
12 |
13 | import android.content.Context
14 | import android.text.method.LinkMovementMethod
15 | import android.widget.LinearLayout
16 | import android.widget.LinearLayout.LayoutParams.MATCH_PARENT
17 | import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
18 | import android.widget.TextView
19 | import androidx.core.content.ContextCompat
20 | import androidx.core.content.res.ResourcesCompat
21 | import com.aliucord.Constants
22 | import com.aliucord.Utils
23 | import com.aliucord.utils.DimenUtils
24 | import com.aliucord.views.Divider
25 | import com.aliucord.views.ToolbarButton
26 | import com.discord.utilities.color.ColorCompat
27 | import com.discord.views.CheckedSetting
28 | import com.google.android.material.card.MaterialCardView
29 | import com.lytefast.flexinput.R
30 |
31 | class ThemeCard(ctx: Context) : MaterialCardView(ctx) {
32 | val switch: CheckedSetting
33 | val author: TextView
34 | val editButton: ToolbarButton
35 | val uninstallButton: ToolbarButton
36 |
37 | init {
38 | radius = DimenUtils.defaultCardRadius.toFloat()
39 | setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundSecondary))
40 | layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
41 |
42 | val p = DimenUtils.defaultPadding
43 |
44 | val linearLayout = com.aliucord.widgets.LinearLayout(ctx).apply {
45 | switch = CheckedSetting(ctx, null).apply {
46 | removeAllViews()
47 | f(CheckedSetting.ViewType.SWITCH)
48 | setSubtext(null)
49 |
50 | l.a().run {
51 | textSize = 16.0f
52 | typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.whitney_semibold)
53 | movementMethod = LinkMovementMethod.getInstance()
54 | }
55 |
56 | l.b().run {
57 | setPadding(0, paddingTop, paddingRight, paddingBottom)
58 | setBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundSecondaryAlt))
59 | }
60 | }
61 |
62 | val subLayout = LinearLayout(ctx).apply {
63 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
64 | }
65 |
66 | author = TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Addition).apply {
67 | setPadding(p, p, p, p)
68 | layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT).apply {
69 | weight = 1f
70 | }
71 | }
72 |
73 | editButton = ToolbarButton(ctx).apply {
74 | setPadding(p, p, p / 2, p)
75 | layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
76 | ContextCompat.getDrawable(ctx, R.e.ic_edit_24dp)!!.mutate().let {
77 | Utils.tintToTheme(it)
78 | setImageDrawable(it, false)
79 | }
80 | }
81 |
82 | uninstallButton = ToolbarButton(ctx).apply {
83 | setPadding(p / 2, p, p, p)
84 | layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
85 | ContextCompat.getDrawable(ctx, R.e.ic_delete_24dp)!!.mutate().let {
86 | Utils.tintToTheme(it)
87 | setImageDrawable(it, false)
88 | }
89 | }
90 |
91 | addView(switch)
92 | addView(Divider(ctx))
93 | addView(subLayout.apply {
94 | addView(author)
95 | addView(editButton)
96 | addView(uninstallButton)
97 | })
98 | }
99 |
100 | addView(linearLayout)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/ThemeLayout.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.view.ViewGroup
16 | import androidx.recyclerview.widget.RecyclerView
17 | import com.aliucord.Utils
18 | import com.aliucord.fragments.ConfirmDialog
19 | import com.aliucord.fragments.SettingsPage
20 | import dev.vendicated.aliucordplugs.themer.Theme
21 | import dev.vendicated.aliucordplugs.themer.settings.editor.ThemeEditor
22 |
23 | class ThemeAdapter(private val fragment: SettingsPage, private val themes: MutableList) : RecyclerView.Adapter() {
24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ThemeViewHolder(this, ThemeCard(parent.context))
25 |
26 | @SuppressLint("SetTextI18n")
27 | override fun onBindViewHolder(holder: ThemeViewHolder, position: Int) = themes[position].let { theme ->
28 | holder.card.run {
29 | switch.isChecked = theme.isEnabled
30 | switch.setText("${theme.name} v${theme.version}")
31 | var text = "by ${theme.author}"
32 | theme.license?.let {
33 | text += " - ${theme.license}"
34 | }
35 | author.text = text
36 | }
37 | }
38 |
39 | override fun getItemCount() = themes.size
40 |
41 | fun onEdit(ctx: Context, position: Int) = Utils.openPageWithProxy(ctx, ThemeEditor(themes[position]))
42 |
43 | fun onUninstall(position: Int) {
44 | val theme = themes[position]
45 | val dialog = ConfirmDialog()
46 | .setIsDangerous(true)
47 | .setTitle("Delete ${theme.name}")
48 | .setDescription("Are you sure you want to delete this theme? This action cannot be undone!")
49 | dialog.setOnOkListener {
50 | dialog.dismiss()
51 | if (theme.file.delete()) {
52 | themes.removeAt(position)
53 | Utils.showToast("Deleted theme ${theme.name}!")
54 | notifyItemRemoved(position)
55 | } else {
56 | Utils.showToast("Failed to delete theme ${theme.name} :(")
57 | }
58 | }
59 | dialog.show(fragment.parentFragmentManager, "Confirm Theme Uninstall")
60 | }
61 |
62 | fun onToggle(position: Int, bool: Boolean) {
63 | themes[position].isEnabled = bool
64 | ThemerSettings.promptRestart(fragment.linearLayout, fragment)
65 | }
66 | }
67 |
68 | class ThemeViewHolder(private val adapter: ThemeAdapter, val card: ThemeCard) : RecyclerView.ViewHolder(card) {
69 | init {
70 | card.run {
71 | editButton.setOnClickListener {
72 | adapter.onEdit(it.context, adapterPosition)
73 | }
74 | uninstallButton.setOnClickListener {
75 | adapter.onUninstall(adapterPosition)
76 | }
77 | switch.setOnCheckedListener {
78 | adapter.onToggle(adapterPosition, it)
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/ThemeEditor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor
12 |
13 | import android.annotation.SuppressLint
14 | import android.content.Context
15 | import android.view.*
16 | import android.widget.TextView
17 | import androidx.core.content.ContextCompat
18 | import androidx.recyclerview.widget.*
19 | import com.aliucord.Utils
20 | import com.aliucord.fragments.SettingsPage
21 | import com.aliucord.utils.MDUtils
22 | import com.aliucord.views.*
23 | import com.discord.utilities.color.ColorCompat
24 | import com.lytefast.flexinput.R
25 | import dev.vendicated.aliucordplugs.themer.*
26 | import dev.vendicated.aliucordplugs.themer.settings.ThemerSettings
27 | import dev.vendicated.aliucordplugs.themer.settings.editor.tabs.*
28 | import dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color.ColorDialogType
29 | import dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color.ColorTab
30 | import org.json.JSONObject
31 |
32 | class ThemeEditor(private val theme: Theme) : SettingsPage() {
33 | private val json: JSONObject
34 |
35 | init {
36 | theme.convertIfLegacy()
37 | json = theme.json().apply {
38 | THEME_KEYS.forEach {
39 | if (!has(it)) put(it, JSONObject())
40 | }
41 | }
42 | }
43 |
44 | @SuppressLint("SetTextI18n")
45 | override fun onViewBound(view: View) {
46 | super.onViewBound(view)
47 |
48 | setActionBarTitle("Theme Editor")
49 | setActionBarSubtitle(theme.name)
50 |
51 | val ctx = view.context
52 |
53 | TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Addition).run {
54 | text =
55 | MDUtils.render("**Allowed hosts for images/fonts/sounds:**\n${ALLOWED_RESOURCE_DOMAINS.joinToString()}")
56 | addView(this)
57 | }
58 |
59 | addView(buildEntry(ctx, "Manifest", R.e.ic_audit_logs_24dp) {
60 | ManifestTab(json.getJSONObject("manifest"))
61 | .show(parentFragmentManager, "Edit Manifest")
62 | })
63 |
64 | addView(buildEntry(ctx, "Background", R.e.ic_text_image_24dp) {
65 | BackgroundTab(json.getJSONObject("background"))
66 | .show(parentFragmentManager, "Edit background")
67 | })
68 |
69 | addView(buildEntry(ctx, "Fonts", R.e.ic_edit_24dp) {
70 | FontTab(json.getJSONObject("fonts"))
71 | .show(parentFragmentManager, "Edit fonts")
72 | })
73 |
74 | addView(buildEntry(ctx, "Sounds", R.e.ic_sound_24dp) {
75 | RawsTab(json.getJSONObject("raws"))
76 | .show(parentFragmentManager, "Edit sounds")
77 | })
78 |
79 | addView(buildColorEntry(ctx, ColorDialogType.SIMPLE_COLORS, "Simple Colors", R.e.ic_accessibility_24dp, lazy {
80 | SIMPLE_KEYS.toList()
81 | }))
82 | addView(buildColorEntry(ctx, ColorDialogType.COLORS, "Colors", R.e.ic_theme_24dp, lazy {
83 | R.c::class.java.declaredFields.map { it.name }
84 | }))
85 | addView(buildColorEntry(ctx, ColorDialogType.DRAWABLES, "Drawable Tints", R.e.ic_emoji_24dp, lazy {
86 | R.e::class.java.declaredFields.map { it.name }
87 | }))
88 |
89 | SaveButton(ctx).run {
90 | setOnClickListener {
91 | theme.file.writeText(json.toString(4))
92 | ThemerSettings.promptRestart(view, this@ThemeEditor, "Saved. Restart?")
93 | }
94 | linearLayout.addView(this)
95 | }
96 | }
97 |
98 | private fun buildColorEntry(ctx: Context, type: ColorDialogType, title: String, drawableId: Int, autocompleteOptions: Lazy>) =
99 | buildEntry(ctx, title, drawableId) {
100 | val key = title.lowercase().replace(" ", "_")
101 | Utils.openPageWithProxy(it.context, ColorTab(type, title, json.getJSONObject(key), autocompleteOptions))
102 | }
103 |
104 | private fun buildEntry(ctx: Context, title: String, drawableId: Int, onClick: View.OnClickListener) =
105 | TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon).apply {
106 | text = title
107 | ContextCompat.getDrawable(ctx, drawableId)?.mutate()?.let {
108 | it.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal))
109 | setCompoundDrawablesRelativeWithIntrinsicBounds(it, null, null, null)
110 | }
111 | setOnClickListener(onClick)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/FormInputTabs.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs
12 |
13 | import android.os.Bundle
14 | import android.text.Editable
15 | import android.text.TextWatcher
16 | import android.view.View
17 | import android.widget.LinearLayout
18 | import com.aliucord.Constants
19 | import com.aliucord.utils.DimenUtils
20 | import com.aliucord.views.TextInput
21 | import dev.vendicated.aliucordplugs.themer.ALLOWED_RESOURCE_DOMAINS_PATTERN
22 | import dev.vendicated.aliucordplugs.themer.SIMPLE_SOUND_NAMES
23 | import org.json.JSONObject
24 | import java.io.File
25 | import java.lang.reflect.Modifier
26 | import java.util.regex.Pattern
27 |
28 | open class FormInputTab(
29 | header: String,
30 | private val keys: Array,
31 | private val validator: (key: String, s: String) -> Boolean,
32 | private val data: JSONObject
33 | ) : Tab(header) {
34 | override fun onViewCreated(view: View, bundle: Bundle?) {
35 | super.onViewCreated(view, bundle)
36 |
37 | val ctx = view.context
38 | val p = DimenUtils.defaultPadding
39 | val p2 = p / 2
40 |
41 | keys.forEach {
42 | TextInput(ctx, it).run {
43 | layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
44 | setMargins(p, p2, p, p2)
45 | }
46 | val input = editText
47 | input.setText(data.optString(it))
48 | input.addTextChangedListener(object : TextWatcher {
49 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
50 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
51 | override fun afterTextChanged(_editable: Editable?) {
52 | root.hint = it
53 | val s = input.text.toString()
54 | when {
55 | s.isEmpty() -> data.remove(it)
56 | !validator.invoke(it, s) -> root.hint = "$it [INVALID]"
57 | else -> data.put(it, converters[it]?.invoke(s) ?: s)
58 | }
59 | }
60 | })
61 |
62 | linearLayout.addView(this)
63 | }
64 | }
65 | }
66 | }
67 |
68 | class ManifestTab(data: JSONObject) : FormInputTab(
69 | "Manifest", arrayOf(
70 | "name",
71 | "author",
72 | "version",
73 | "license",
74 | "updater"
75 | ), Validators::manifest, data
76 | )
77 |
78 | class BackgroundTab(data: JSONObject) : FormInputTab(
79 | "Background", arrayOf("url", "overlay_alpha", "blur_radius"),
80 | Validators::background, data
81 | )
82 |
83 | class FontTab(data: JSONObject) : FormInputTab(
84 | "Fonts",
85 | run {
86 | try {
87 | val fonts = Constants.Fonts::class.java.declaredFields
88 | val names = Array(fonts.size) { "" }
89 | names[0] = "*"
90 | fonts.forEachIndexed { i, f ->
91 | if (Modifier.isPublic(f.modifiers)) {
92 | names[i] = f.name
93 | }
94 | }
95 | names
96 | } catch (th: Throwable) {
97 | emptyArray()
98 | }
99 | }, Validators::fonts, data
100 | )
101 |
102 | class RawsTab(data: JSONObject) : FormInputTab(
103 | "Sounds", SIMPLE_SOUND_NAMES,
104 | Validators::raws, data
105 | )
106 |
107 | object Validators {
108 | private fun tryOrFalse(fn: () -> Boolean) = try {
109 | fn.invoke()
110 | } catch (e: Throwable) {
111 | false
112 | }
113 |
114 | private val versionPattern: Pattern by lazy {
115 | Pattern.compile("^(\\d{1,2}\\.)+\\d{1,2}$")
116 | }
117 |
118 | private fun urlValidator(s: String) =
119 | s.isEmpty() ||
120 | ALLOWED_RESOURCE_DOMAINS_PATTERN.matcher(s).find() ||
121 | (s.startsWith("file://") && File(s.removePrefix("file://")).exists())
122 |
123 | fun manifest(key: String, s: String) = when (key) {
124 | "version" -> versionPattern.matcher(s).matches()
125 | "updater" -> urlValidator(s)
126 | else -> true
127 | }
128 |
129 | fun background(key: String, s: String) = when (key) {
130 | "url" -> urlValidator(s)
131 | "overlay_alpha" -> tryOrFalse { s.toInt() in 0..0xFF }
132 | "blur_radius" -> tryOrFalse { s.toDouble() in 0.0..25.0 }
133 | else -> throw NotImplementedError(key)
134 | }
135 |
136 | fun fonts(_key: String, s: String) = urlValidator(s)
137 |
138 | fun raws(_key: String, s: String) = urlValidator(s)
139 | }
140 |
141 | val converters = mapOf Any>(
142 | "overlay_alpha" to { it.toInt() },
143 | "blur_radius" to { it.toDouble() }
144 | )
145 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/Tab.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs
12 |
13 | import android.os.Bundle
14 | import android.view.View
15 | import android.widget.TextView
16 | import androidx.core.content.res.ResourcesCompat
17 | import com.aliucord.Constants
18 | import com.aliucord.widgets.BottomSheet
19 | import com.lytefast.flexinput.R
20 |
21 | abstract class Tab(private val header: String) : BottomSheet() {
22 | override fun onViewCreated(view: View, bundle: Bundle?) {
23 | super.onViewCreated(view, bundle)
24 |
25 | val ctx = view.context
26 | TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Header).apply {
27 | text = header
28 | typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.whitney_semibold)
29 | }.also {
30 | addView(it)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/color/ColorAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color
12 |
13 | import android.content.Context
14 | import android.view.ViewGroup
15 | import android.widget.*
16 | import android.widget.LinearLayout.LayoutParams.MATCH_PARENT
17 | import androidx.fragment.app.FragmentManager
18 | import androidx.recyclerview.widget.DiffUtil
19 | import androidx.recyclerview.widget.RecyclerView
20 | import com.aliucord.Utils
21 | import com.aliucord.utils.DimenUtils
22 | import com.discord.utilities.colors.ColorPickerUtils
23 | import com.jaredrummler.android.colorpicker.ColorPickerDialog
24 | import org.json.JSONObject
25 |
26 | class ColorAdapter(
27 | private val fragmentManager: FragmentManager,
28 | val json: JSONObject,
29 | val items: ArrayList
30 | ) : RecyclerView.Adapter(), Filterable {
31 | private val layoutHeight = DimenUtils.dpToPx(64)
32 | var filteredItems = ArrayList(items)
33 |
34 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
35 | ColorViewHolder(this, com.aliucord.widgets.LinearLayout(parent.context).apply {
36 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, layoutHeight)
37 | })
38 |
39 | override fun onBindViewHolder(holder: ColorViewHolder, position: Int) =
40 | filteredItems[position].let { (name, color) ->
41 | holder.textView.text = name
42 | holder.setColor(color)
43 | }
44 |
45 | override fun getItemCount() = filteredItems.size
46 |
47 | fun addItem(item: ColorTuple) {
48 | items.add(0, item)
49 | val shouldShow = currentFilter?.let { item.name.contains(it, true) } ?: true
50 | if (shouldShow) {
51 | filteredItems.add(0, item)
52 | notifyItemInserted(0)
53 | }
54 | }
55 |
56 | fun onEntryClicked(position: Int, ctx: Context) =
57 | filteredItems[position].let {
58 | val colorPicker =
59 | ColorPickerUtils.INSTANCE.buildColorPickerDialog(
60 | ctx,
61 | Utils.getResId("color_picker_title", "string"),
62 | it.color
63 | )
64 | colorPicker.arguments?.putBoolean("alpha", true) // showAlphaSlider
65 | colorPicker
66 | .setListener(ColorPickerListener(this, position))
67 | .show(fragmentManager, "Color Picker")
68 | }
69 |
70 | fun onEntryDeleted(position: Int) {
71 | val item = filteredItems[position]
72 | json.remove(item.name)
73 | filteredItems.removeAt(position)
74 | items.remove(item)
75 | notifyItemRemoved(position)
76 | }
77 |
78 | override fun getFilter(): Filter = filter
79 |
80 | private var currentFilter: CharSequence? = null
81 | private val filter = object : Filter() {
82 | override fun performFiltering(constraint: CharSequence?) = FilterResults().apply {
83 | currentFilter = constraint
84 | if (constraint == null) {
85 | count = items.size
86 | values = items
87 | } else {
88 | values = items.filter {
89 | it.name.contains(constraint, true)
90 | }.apply {
91 | count = size
92 | }
93 | }
94 | }
95 |
96 | override fun publishResults(constraint: CharSequence?, results: FilterResults) {
97 | val data = results.values as ArrayList<*>
98 | DiffUtil.calculateDiff(object : DiffUtil.Callback() {
99 | override fun getOldListSize() = filteredItems.size
100 |
101 | override fun getNewListSize() = results.count
102 |
103 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
104 | filteredItems[oldItemPosition].equals(data[newItemPosition])
105 |
106 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true
107 | }).dispatchUpdatesTo(this@ColorAdapter)
108 | filteredItems = data as ArrayList
109 | }
110 | }
111 | }
112 |
113 | private fun ColorPickerDialog.setListener(listener: b.k.a.a.f) = apply {
114 | k = listener
115 | }
116 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/color/ColorPickerListener.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color
12 |
13 | class ColorPickerListener(
14 | private val adapter: ColorAdapter,
15 | private val position: Int
16 | ) : b.k.a.a.f {
17 | override fun onColorReset(color: Int) {}
18 |
19 | override fun onColorSelected(_id: Int, color: Int) {
20 | adapter.filteredItems[position].let {
21 | it.color = color
22 | adapter.json.put(it.name, color)
23 | }
24 | adapter.notifyItemChanged(position)
25 | }
26 |
27 | override fun onDialogDismissed(_id: Int) {}
28 | }
29 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/color/ColorTab.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color
12 |
13 | import android.graphics.Color
14 | import android.os.Bundle
15 | import android.text.Editable
16 | import android.text.TextWatcher
17 | import android.view.*
18 | import android.widget.LinearLayout
19 | import androidx.recyclerview.widget.LinearLayoutManager
20 | import androidx.recyclerview.widget.RecyclerView
21 | import com.aliucord.Utils
22 | import com.aliucord.fragments.SettingsPage
23 | import com.aliucord.utils.DimenUtils
24 | import com.aliucord.views.TextInput
25 | import com.google.android.material.floatingactionbutton.FloatingActionButton
26 | import com.lytefast.flexinput.R
27 | import dev.vendicated.aliucordplugs.themer.toColorArray
28 | import org.json.JSONObject
29 |
30 | private val layoutId: Int by lazy {
31 | Utils.getResId("widget_server_settings_roles", "layout")
32 | }
33 |
34 | private val recyclerId: Int by lazy {
35 | Utils.getResId("server_settings_roles_recycler", "id")
36 | }
37 |
38 | private val fabId: Int by lazy {
39 | Utils.getResId("roles_list_add_role_fab", "id")
40 | }
41 |
42 | class ColorTab(
43 | private val type: ColorDialogType,
44 | private val header: String,
45 | private val data: JSONObject,
46 | private val autoCompleteOptions: Lazy>
47 | ) : SettingsPage() {
48 | override fun onViewCreated(view: View, bundle: Bundle?) {
49 | super.onViewCreated(view, bundle)
50 |
51 | setActionBarTitle(header)
52 | setActionBarSubtitle("Editor")
53 | setPadding(0)
54 |
55 | val ctx = view.context
56 | val p = DimenUtils.defaultPadding
57 | val p2 = p / 2
58 |
59 | val items = data.toColorArray()
60 | val adapter = ColorAdapter(parentFragmentManager, data, items)
61 |
62 | TextInput(ctx).apply {
63 | layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
64 | setMargins(p, p2, p, p2)
65 | }
66 | root.hint = ctx.getString(R.h.search)
67 | val input = editText
68 | input.addTextChangedListener(object : TextWatcher {
69 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
70 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
71 | override fun afterTextChanged(_editable: Editable?) {
72 | adapter.filter.filter(input.text.toString())
73 | }
74 | })
75 | }.also { addView(it) }
76 |
77 | val layout = LayoutInflater.from(ctx)
78 | .inflate(layoutId, linearLayout, false)
79 | .also { addView(it) } as ViewGroup
80 |
81 |
82 | layout.getChildAt(0).layoutParams.height = 0
83 |
84 | layout.findViewById(recyclerId).apply {
85 | this.adapter = adapter
86 | layoutManager = LinearLayoutManager(ctx)
87 | }
88 |
89 | layout.findViewById(fabId).run {
90 | setOnClickListener {
91 | NewColorDialog(type, autoCompleteOptions.value) {
92 | adapter.addItem(ColorTuple(it, Color.BLACK))
93 | }.show(parentFragmentManager, "New Color")
94 | }
95 | show()
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/color/ColorTuple.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color
12 |
13 | data class ColorTuple(var name: String, var color: Int)
14 |
--------------------------------------------------------------------------------
/Themer/src/main/kotlin/dev/vendicated/aliucordplugs/themer/settings/editor/tabs/color/ColorViewHolder.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | package dev.vendicated.aliucordplugs.themer.settings.editor.tabs.color
12 |
13 | import android.graphics.*
14 | import android.graphics.drawable.ShapeDrawable
15 | import android.graphics.drawable.shapes.OvalShape
16 | import android.view.Gravity
17 | import android.view.View
18 | import android.widget.LinearLayout
19 | import android.widget.RelativeLayout.LayoutParams.MATCH_PARENT
20 | import android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT
21 | import android.widget.TextView
22 | import androidx.core.content.ContextCompat
23 | import androidx.core.graphics.ColorUtils
24 | import androidx.core.widget.TextViewCompat
25 | import androidx.recyclerview.widget.RecyclerView
26 | import com.aliucord.utils.DimenUtils
27 | import com.aliucord.views.ToolbarButton
28 | import com.discord.utilities.color.ColorCompat
29 | import com.lytefast.flexinput.R
30 |
31 | class ColorViewHolder(private val adapter: ColorAdapter, layout: LinearLayout) : RecyclerView.ViewHolder(layout) {
32 | val textView: TextView
33 | private val colorCircleView: LinearLayout
34 |
35 | init {
36 | val ctx = layout.context
37 | val p = DimenUtils.dpToPx(16)
38 | val dp48 = DimenUtils.dpToPx(48)
39 |
40 | layout.orientation = LinearLayout.HORIZONTAL
41 | layout.gravity = Gravity.CENTER_VERTICAL
42 |
43 | ToolbarButton(ctx).run {
44 | setImageDrawable(ContextCompat.getDrawable(ctx, R.e.ic_x_red_24dp), false)
45 | layoutParams = LinearLayout.LayoutParams(dp48, dp48).apply {
46 | marginStart = p
47 | }
48 | setOnClickListener {
49 | adapter.onEntryDeleted(adapterPosition)
50 | }
51 | layout.addView(this)
52 | }
53 |
54 | textView = TextView(ctx, null, 0, R.i.UiKit_Settings_Item).apply {
55 | layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
56 | weight = 1f
57 | }
58 | setOnClickListener {
59 | adapter.onEntryClicked(adapterPosition, it.context)
60 | }
61 | layout.addView(this)
62 |
63 | TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM)
64 | }
65 |
66 | colorCircleView = LinearLayout(ctx).apply {
67 | background = ShapeDrawable(OvalShape())
68 | layoutParams = LinearLayout.LayoutParams(dp48, dp48).apply {
69 | marginEnd = p
70 | }
71 | setOnClickListener {
72 | adapter.onEntryClicked(adapterPosition, it.context)
73 | }
74 | View(ctx).apply {
75 | layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT).apply {
76 | DimenUtils.dpToPx(1).let {
77 | setMargins(it, it, it, it)
78 | }
79 | }
80 | background = ShapeDrawable(OvalShape())
81 | }.also {
82 | addView(it)
83 | }
84 |
85 | layout.addView(this)
86 | }
87 | }
88 |
89 | fun setColor(color: Int) {
90 | val fullAlphaColor = ColorUtils.setAlphaComponent(color, 0xFF)
91 | /* Calculate contrast between background and the colour. If the contrast is too low
92 | * (colours are too similar), calculate the colour's luminance and add a white/black outline
93 | * accordingly.
94 | */
95 | val bgColor = ColorUtils.setAlphaComponent(
96 | ColorCompat.getThemedColor(colorCircleView.context, R.c.primary_dark_600),
97 | 0xFF
98 | )
99 |
100 | val contrastColor =
101 | when {
102 | ColorUtils.calculateContrast(color, bgColor) > 2f -> fullAlphaColor
103 | Color.luminance(color) < 0.5f -> Color.WHITE
104 | else -> Color.BLACK
105 | }
106 |
107 | colorCircleView.background.colorFilter =
108 | PorterDuffColorFilter(contrastColor, PorterDuff.Mode.SRC_ATOP)
109 |
110 | colorCircleView.getChildAt(0).background.colorFilter =
111 | PorterDuffColorFilter(fullAlphaColor, PorterDuff.Mode.SRC_ATOP)
112 |
113 | val alpha = Color.alpha(color)
114 | if (alpha != 255) {
115 | colorCircleView.background.alpha = alpha
116 | colorCircleView.getChildAt(0).background.alpha = alpha
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/UrbanDictionary/README.md:
--------------------------------------------------------------------------------
1 | # UrbanDictionary - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/UrbanDictionary.zip?raw=true)
2 |
3 | Usage:
4 | ```
5 | /haste [send]
6 | ```
7 |
8 | If send is set to true, the definition will be sent in plain text visible for everyone, otherwise it will be sent in an Embed only visible for you
9 |
10 | 
--------------------------------------------------------------------------------
/UrbanDictionary/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.7";
2 | description = "Get definitions from urbandictionary.com";
3 |
--------------------------------------------------------------------------------
/UrbanDictionary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/UrbanDictionary/src/main/java/dev/vendicated/aliucordplugs/urbandictionary/ApiResponse.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 | package dev.vendicated.aliucordplugs.urbandictionary
11 |
12 | class ApiResponse(val list: List) {
13 | data class Definition(
14 | val definition: String,
15 | val permalink: String,
16 | val word: String,
17 | val thumbs_up: Int,
18 | val thumbs_down: Int,
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/UrbanDictionary/src/main/java/dev/vendicated/aliucordplugs/urbandictionary/UrbanDictionary.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 | package dev.vendicated.aliucordplugs.urbandictionary
11 |
12 | import android.content.Context
13 | import com.aliucord.Http
14 | import com.aliucord.Http.QueryBuilder
15 | import com.aliucord.Utils
16 | import com.aliucord.annotations.AliucordPlugin
17 | import com.aliucord.api.CommandsAPI.CommandResult
18 | import com.aliucord.entities.MessageEmbedBuilder
19 | import com.aliucord.entities.Plugin
20 | import com.discord.api.commands.ApplicationCommandType
21 | import com.discord.api.message.embed.MessageEmbed
22 | import java.io.IOException
23 | import java.io.UnsupportedEncodingException
24 | import java.net.URLEncoder
25 |
26 | private const val baseUrl = "https://api.urbandictionary.com/v0/define"
27 | private const val thumbsUp = "\uD83D\uDC4D"
28 | private const val thumbsDown = "\uD83D\uDC4E"
29 |
30 | @AliucordPlugin
31 | class UrbanDictionary : Plugin() {
32 | override fun start(context: Context) {
33 | val arguments = listOf(
34 | Utils.createCommandOption(
35 | ApplicationCommandType.STRING,
36 | "search",
37 | "The word to search for",
38 | required = true,
39 | default = true
40 | ),
41 | Utils.createCommandOption(
42 | ApplicationCommandType.BOOLEAN,
43 | "send",
44 | "Whether the result should be visible for everyone",
45 | )
46 | )
47 |
48 | commands.registerCommand(
49 | "urban",
50 | "Get a definition from urbandictionary.com",
51 | arguments
52 | ) { ctx ->
53 | val search = ctx.getRequiredString("search")
54 | var send = ctx.getBoolOrDefault("send", false)
55 | var embed: List? = null
56 | var result: String
57 | try {
58 | val res = Http.simpleJsonGet(QueryBuilder(baseUrl).append("term", search).toString(), ApiResponse::class.java)
59 | if (res.list.isEmpty()) {
60 | result = "No definition found for `$search`"
61 | send = false
62 | } else {
63 | val data = res.list[0]
64 | val votes = "$thumbsUp ${data.thumbs_up} | $thumbsDown ${data.thumbs_down}"
65 | if (send) {
66 | result =
67 | """
68 | **__"${data.word}" on urban dictionary:__**
69 | >>> ${trimLong(data.definition.replace("[", "").replace("]", ""))}
70 |
71 | <${data.permalink}>
72 |
73 | $votes
74 | """
75 | } else {
76 | result = "I found the following:"
77 | embed = listOf(
78 | MessageEmbedBuilder()
79 | .setTitle(data.word)
80 | .setUrl(data.permalink)
81 | .setDescription(formatUrls(data.definition))
82 | .setFooter(votes, null, null)
83 | .build()
84 | )
85 | }
86 | }
87 | } catch (t: IOException) {
88 | result = "Something went wrong: " + t.message
89 | send = false
90 | }
91 |
92 | CommandResult(result, embed, send, "UrbanDictionary", "https://www.urbandictionary.com/favicon.ico")
93 | }
94 | }
95 |
96 | override fun stop(context: Context) {
97 | commands.unregisterAll()
98 | }
99 |
100 | private fun trimLong(str: String): String {
101 | return if (str.length < 1000) str else str.substring(0, 1000 - 3) + "..."
102 | }
103 |
104 | private fun formatUrls(raw: String) =
105 | raw.replace(Regex("\\[.+?\\]")) {
106 | val value = it.value
107 | "[$value](https://www.urbandictionary.com/define.php?term=${encodeUri(value.substring(1, value.length - 1))})"
108 | }
109 |
110 | private fun encodeUri(raw: String): String {
111 | return try {
112 | URLEncoder.encode(raw, "UTF-8")
113 | } catch (ignored: UnsupportedEncodingException) {
114 | throw AssertionError("UTF-8 is not supported somehow")
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/ViewProfileImages/README.md:
--------------------------------------------------------------------------------
1 | # ViewProfileImages - [Download](https://github.com/Vendicated/AliucordPlugins/blob/builds/ViewProfileImages.zip?raw=true)
2 |
3 | Allows you to open user or server avatars/banners by tapping them, similar to attachments or embeds.
4 |
5 | 
--------------------------------------------------------------------------------
/ViewProfileImages/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "1.0.4";
2 | description = "Allows opening avatars/icons and banners by clicking them in the user/server profile sheet";
3 |
--------------------------------------------------------------------------------
/ViewProfileImages/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vendicated/AliucordPlugins/a1decb53c13219145ef2336c818d5e2b348f61cc/ViewProfileImages/demo.gif
--------------------------------------------------------------------------------
/ViewProfileImages/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.BaseExtension
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | maven("https://maven.aliucord.com/snapshots")
8 | maven("https://jitpack.io")
9 | }
10 | dependencies {
11 | classpath("com.android.tools.build:gradle:7.0.4")
12 | classpath("com.aliucord:gradle:main-SNAPSHOT") {
13 | exclude("com.github.js6pak", "jadb")
14 | }
15 | classpath("com.aliucord:jadb:1.2.1-SNAPSHOT")
16 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21")
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | mavenCentral()
24 | mavenLocal()
25 | maven("https://maven.aliucord.com/snapshots")
26 | maven("https://jitpack.io")
27 | }
28 | }
29 |
30 | fun Project.aliucord(configuration: com.aliucord.gradle.AliucordExtension.() -> Unit) =
31 | extensions.getByName("aliucord").configuration()
32 | fun Project.android(configuration: BaseExtension.() -> Unit) =
33 | extensions.getByName("android").configuration()
34 |
35 | subprojects {
36 | apply(plugin = "com.android.library")
37 | apply(plugin = "com.aliucord.gradle")
38 | apply(plugin = "kotlin-android")
39 |
40 | aliucord {
41 | author("Vendicated", 343383572805058560L)
42 | updateUrl.set("https://raw.githubusercontent.com/Vendicated/AliucordPlugins/builds/updater.json")
43 | buildUrl.set("https://raw.githubusercontent.com/Vendicated/AliucordPlugins/builds/%s.zip")
44 | }
45 |
46 | android {
47 | compileSdkVersion(30)
48 |
49 | defaultConfig {
50 | minSdk = 24
51 | targetSdk = 30
52 | }
53 |
54 | compileOptions {
55 | sourceCompatibility = JavaVersion.VERSION_11
56 | targetCompatibility = JavaVersion.VERSION_11
57 | }
58 |
59 | tasks.withType {
60 | kotlinOptions {
61 | jvmTarget = "11"
62 | freeCompilerArgs = freeCompilerArgs +
63 | "-Xno-call-assertions" +
64 | "-Xno-param-assertions" +
65 | "-Xno-receiver-assertions"
66 | }
67 | }
68 | }
69 |
70 | dependencies {
71 | val discord by configurations
72 | val implementation by configurations
73 | val api by configurations
74 |
75 | discord("com.discord:discord:aliucord-SNAPSHOT")
76 | implementation("com.aliucord:Aliucord:main-SNAPSHOT")
77 | // implementation("com.aliucord:Aliucord:unspecified")
78 |
79 | implementation("androidx.appcompat:appcompat:1.4.1")
80 | implementation("com.google.android.material:material:1.5.0")
81 | implementation("androidx.constraintlayout:constraintlayout:2.1.3")
82 | }
83 | }
84 |
85 | task("clean") {
86 | delete(rootProject.buildDir)
87 | }
88 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. More details, visit
11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
12 | # org.gradle.parallel=true
13 | #Thu Jun 10 00:25:10 CEST 2021
14 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -Dfile.encoding\=UTF-8
15 | android.useAndroidX=true
16 | android.enableJetifier=true
17 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vendicated/AliucordPlugins/a1decb53c13219145ef2336c818d5e2b348f61cc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Apr 30 20:08:06 CEST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/scripts/licenseHeader.txt:
--------------------------------------------------------------------------------
1 | /*
2 | * Ven's Aliucord Plugins
3 | * Copyright (C) 2021 Vendicated
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 |
--------------------------------------------------------------------------------
/scripts/licenser.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 | shopt -s globstar
3 |
4 | for file in $(git diff --staged --diff-filter=ACMR --name-only | grep -E ".(java|kt)$")
5 | do
6 | if [ -f "$file" ] && ! grep -q Copyright "$file"
7 | then
8 | cat scripts/licenseHeader.txt "$file" > "$file.licensed" && mv "$file.licensed" "$file"
9 | git add "$file"
10 | echo "Added license header to $file"
11 | else
12 | echo "Skipped $file as it already had a license header"
13 | fi
14 | done
15 |
--------------------------------------------------------------------------------
/scripts/new.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | name="$1"
5 | [ -z "$name" ] && { >&2 echo "Please specify a name"; exit 1; }
6 | first_char="$(printf %.1s "$name")"
7 | [ "$(echo "$first_char" | tr "[:lower:]" "[:upper:]")" != "$first_char" ] && { >&2 echo "Name must be PascalCase"; exit 1; }
8 |
9 | pkg="$(echo "$name" | tr "[:upper:]" "[:lower:]")"
10 | d="$name/src/main/kotlin/dev/vendicated/aliucordplugins"
11 | new="$d/$pkg/$name.kt"
12 |
13 | set -x
14 | cp -r Template "$name"
15 |
16 | # Renaming file
17 | mv "$d/template" "$d/$pkg"
18 | mv "$d/$pkg/Template.kt" "$new"
19 | # Change class name
20 | sed -i "s/Template/$name/" "$new"
21 | # Change pkg name
22 | sed -i "s/aliucordplugins.template/aliucordplugins.$pkg/" "$new"
23 |
24 | # Add to settings.gradle
25 | echo "include(\":$name\")" | cat - settings.gradle.kts > settings.gradle.new && mv settings.gradle.new settings.gradle.kts
26 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":GatewayLog")
2 | include(":TextFilePreview")
3 | // include(":SnowflakeLookup")
4 | include(":FixEmotes")
5 | include(":PlayableEmbeds")
6 | // include(":AliuEval")
7 | // include(":OwO")
8 | include(":EmojiReplacer")
9 | include(":ShowBlockedMessages")
10 | include(":BetterSpotify")
11 | include(":Brainfuck")
12 | include(":CheckLinks")
13 | include(":AnimateApngs")
14 | include(":DedicatedPluginSettings")
15 | include(":Themer")
16 | include(":ViewProfileImages")
17 | include(":TapTap")
18 | include(":MessageLinkEmbeds")
19 | include(":EmojiUtility")
20 | include(":Hastebin")
21 | include(":UrbanDictionary")
22 | include(":JsEval")
23 |
24 | rootProject.name = "AliucordPlugins"
25 |
--------------------------------------------------------------------------------
/uwu:
--------------------------------------------------------------------------------
1 | torvalds was here uwu
2 |
--------------------------------------------------------------------------------