├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── mapping.txt ├── proguard-rules-module.pro ├── proguard-rules.pro └── src │ ├── apk │ ├── AndroidManifest.xml │ ├── java │ │ └── tv │ │ │ └── twitch │ │ │ └── android │ │ │ └── app │ │ │ └── run │ │ │ └── MainActivity.java │ └── res │ │ └── values │ │ └── strings.xml │ └── main │ ├── AndroidManifest.xml │ └── java │ └── tv │ └── twitch │ ├── a │ ├── b │ │ └── f │ │ │ └── a.java │ ├── e │ │ ├── g │ │ │ ├── f.java │ │ │ ├── t.java │ │ │ └── v.java │ │ └── l │ │ │ └── c.java │ ├── f │ │ └── g.java │ ├── j │ │ └── u │ │ │ └── a.java │ └── k │ │ ├── e0 │ │ └── b │ │ │ └── r │ │ │ └── h.java │ │ ├── g │ │ ├── a0.java │ │ ├── i1 │ │ │ └── a.java │ │ ├── l1 │ │ │ └── a.java │ │ ├── q0 │ │ │ └── a.java │ │ ├── v1 │ │ │ └── c.java │ │ └── x0 │ │ │ └── d.java │ │ ├── m │ │ └── e.java │ │ └── x │ │ └── v.java │ └── android │ ├── adapters │ └── a │ │ └── b.java │ ├── app │ └── core │ │ └── j2 │ │ └── b.java │ ├── core │ ├── adapters │ │ └── d0.java │ └── crashreporter │ │ └── a.java │ ├── feature │ └── theatre │ │ └── common │ │ ├── d.java │ │ └── i.java │ ├── models │ └── player │ │ └── PlayerImplementation.java │ ├── player │ └── autoplayoverlay │ │ └── RecommendationAutoPlayPresenter.java │ ├── sdk │ └── z.java │ ├── settings │ └── z │ │ └── d.java │ └── shared │ └── chat │ ├── communitypoints │ └── t.java │ └── messageinput │ ├── h.java │ └── u │ └── d.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mod ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── tv │ │ └── twitch │ │ └── android │ │ └── mod │ │ ├── bridges │ │ ├── ApiCallback.java │ │ ├── ChatFactory.java │ │ ├── Hooks.java │ │ ├── IChatMessageFactory.java │ │ ├── IDPub.java │ │ ├── IDrawable.java │ │ ├── IMessageRecyclerItem.java │ │ ├── LoaderLS.java │ │ ├── PlayerWrapper.java │ │ └── RetrofitUtils.java │ │ ├── emotes │ │ ├── BaseChannelSet.java │ │ ├── BaseEmoteSet.java │ │ ├── BttvChannelSet.java │ │ ├── BttvGlobalSet.java │ │ ├── EmoteManager.java │ │ ├── FfzChannelSet.java │ │ └── Room.java │ │ ├── models │ │ ├── BttvEmoteModel.java │ │ ├── Emote.java │ │ ├── EmoteSet.java │ │ ├── FfzEmoteModel.java │ │ ├── PreferenceItem.java │ │ ├── api │ │ │ ├── BttvChannelResponse.java │ │ │ ├── BttvEmoteResponse.java │ │ │ ├── FailReason.java │ │ │ ├── FfzEmoteResponse.java │ │ │ └── ImageType.java │ │ └── settings │ │ │ ├── EmotePickerView.java │ │ │ ├── EmoteSize.java │ │ │ ├── ExoPlayerSpeed.java │ │ │ ├── FollowView.java │ │ │ ├── Gifs.java │ │ │ ├── MiniPlayerSize.java │ │ │ ├── PlayerImpl.java │ │ │ └── UserMessagesFiltering.java │ │ ├── net │ │ ├── BttvApi.java │ │ └── ServiceFactory.java │ │ ├── settings │ │ ├── PrefManager.java │ │ └── SettingsController.java │ │ ├── swipper │ │ ├── Swipper.java │ │ ├── util │ │ │ ├── BrightnessHelper.java │ │ │ └── DimensionConverter.java │ │ └── view │ │ │ ├── SwipperOverlay.java │ │ │ └── VerticalProgressBar.java │ │ └── utils │ │ ├── ChatFilteringUtil.java │ │ ├── ChatUtil.java │ │ ├── Clicker.java │ │ ├── GifHelper.java │ │ ├── Helper.java │ │ └── Logger.java │ └── res │ ├── values-ru │ └── strings.xml │ └── values │ └── strings.xml ├── settings.gradle └── twitch ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml └── java ├── androidx └── fragment │ └── app │ └── FragmentActivity.java ├── com ├── bumptech │ └── glide │ │ └── load │ │ └── n │ │ └── g │ │ └── c.java └── google │ └── android │ └── exoplayer2 │ └── PlaybackParameters.java ├── kotlin └── jvm │ └── c │ └── g.java └── tv └── twitch ├── a ├── b │ └── i │ │ ├── d.java │ │ └── e.java └── k │ ├── e0 │ └── b │ │ └── r │ │ ├── a.java │ │ └── d.java │ ├── g │ ├── d1 │ │ └── b.java │ ├── g.java │ ├── i1 │ │ └── e.java │ └── n0.java │ ├── l │ └── h │ │ └── i │ │ ├── a.java │ │ ├── b.java │ │ └── c.java │ └── m │ └── a.java ├── android ├── api │ └── p1 │ │ └── i1.java ├── app │ └── consumer │ │ └── TwitchApplication.java ├── core │ └── adapters │ │ └── t.java ├── models │ ├── Playable.java │ ├── channel │ │ └── ChannelInfo.java │ ├── chat │ │ └── MessageToken.java │ ├── clips │ │ └── ClipModel.java │ └── communitypoints │ │ └── ActiveClaimModel.java ├── settings │ ├── d.java │ └── l │ │ ├── b.java │ │ └── d.java └── shared │ ├── chat │ └── communitypoints │ │ └── models │ │ └── CommunityPointsModel.java │ └── ui │ └── menus │ ├── j.java │ ├── k │ └── b.java │ ├── l │ └── b.java │ ├── m │ └── a.java │ ├── o │ └── a.java │ └── s │ └── b.java └── chat ├── ChatEmoticon.java ├── ChatEmoticonSet.java ├── ChatLiveMessage.java ├── ChatMessageInfo.java └── ChatUserMode.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.smali 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update 2 | 3 | This mod is being rewritten from scratch on GitLab https://gitlab.com/twitchmod/orange-tv 4 | 5 | Latest version still works and can be downloaded from the official telegram channel https://t.me/pubTw 6 | 7 | 8 | ## TwitchMod 9 | TwitchMod is a modified version of Twitch Android Application. 10 | 11 | **Features:** 12 | - BetterTTV and FrankerFaceZ emotes 13 | - Force ExoPlayer2 14 | - Timestamps 15 | - Prevent messages from being removed 16 | - Copy messages by tapping & holding them 17 | - Autoclicker for channel points 18 | - Floating chat 19 | - Video stats 20 | - And more 21 | 22 | ### Download 23 | - Latest build (9.1.1): https://4pda.ru/forum/index.php?showtopic=320321&view=findpost&p=96966462 (login or register to download this file) 24 | - Latest unstable build: https://mega.nz/folder/f1AnRA4J#49rnejMH3T04qyOJk7Zwkw 25 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | 6 | compileSdkVersion 29 7 | defaultConfig { 8 | applicationId "tv.twitch.android.app" 9 | minSdkVersion 21 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | flavorDimensions "version" 16 | productFlavors { 17 | apk { 18 | applicationIdSuffix ".run" 19 | dimension "version" 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | module { 23 | applicationIdSuffix ".module" 24 | dimension "version" 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-module.pro' 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled true 32 | shrinkResources true 33 | } 34 | debug { 35 | minifyEnabled true 36 | shrinkResources true 37 | } 38 | } 39 | buildToolsVersion = '29.0.3' 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(dir: 'libs', include: ['*.jar']) 44 | implementation project(path: ':mod') 45 | implementation project(path: ':twitch') 46 | } 47 | -------------------------------------------------------------------------------- /app/mapping.txt: -------------------------------------------------------------------------------- 1 | # Version: 9.2.0 2 | 3 | com.google.gson.annotations.SerializedName -> com.google.gson.v.c: 4 | com.google.gson.Gson -> com.google.gson.f: 5 | okhttp3.Request -> j.d0: 6 | retrofit2.Call -> retrofit2.b: 7 | void enqueue(retrofit2.Callback) -> e0 8 | retrofit2.Call clone() -> t 9 | retrofit2.Callback -> retrofit2.d: 10 | void onFailure(retrofit2.Call,java.lang.Throwable) -> a 11 | void onResponse(retrofit2.Call,retrofit2.Response) -> b 12 | retrofit2.Response -> retrofit2.l: 13 | 125:125:int code() -> b 14 | 140:140:boolean isSuccessful() -> f 15 | 145:145:java.lang.Object body() -> a 16 | retrofit2.http.Path -> retrofit2.q.r: 17 | retrofit2.http.Headers -> retrofit2.q.k: 18 | retrofit2.http.GET -> retrofit2.q.f: 19 | retrofit2.http.Query -> retrofit2.q.s: 20 | retrofit2.Retrofit -> retrofit2.m: 21 | 131:135:java.lang.Object create(java.lang.Class) -> d 22 | retrofit2.Retrofit$Builder -> retrofit2.m$b: 23 | 469:470:retrofit2.Retrofit$Builder baseUrl(java.lang.String) -> c 24 | 535:536:retrofit2.Retrofit$Builder addConverterFactory(retrofit2.Converter$Factory) -> b 25 | 586:587:retrofit2.Retrofit build() -> e 26 | retrofit2.Converter$Factory -> retrofit2.e$a: 27 | retrofit2.converter.gson.GsonConverterFactory -> retrofit2.p.a.a: 28 | 51:51:retrofit2.converter.gson.GsonConverterFactory create(com.google.gson.Gson) -> d -------------------------------------------------------------------------------- /app/proguard-rules-module.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | 24 | -keep class okhttp3.** { *; } 25 | -dontwarn okhttp3.internal.platform.ConscryptPlatform 26 | 27 | # Twitch 28 | -keep class tv.twitch.** { *; } 29 | 30 | # Libs 31 | -keep class com.bumptech.glide.** { *; } 32 | -keep class com.google.android.exoplayer2.** { *; } 33 | -keep class androidx.appcompat.app.AppCompatActivity { *; } 34 | -keep class kotlin.** { *; } 35 | 36 | # Twitch obfuscation 37 | -applymapping mapping.txt -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class okhttp3.** { *; } 24 | -dontwarn okhttp3.internal.platform.ConscryptPlatform 25 | 26 | # Twitch 27 | -keep class tv.twitch.** { *; } 28 | 29 | # Libs 30 | -keep class com.bumptech.glide.** { *; } 31 | -keep class com.google.android.exoplayer2.** { *; } 32 | -keep class androidx.appcompat.app.AppCompatActivity { *; } -------------------------------------------------------------------------------- /app/src/apk/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/apk/java/tv/twitch/android/app/run/MainActivity.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.app.run; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | 7 | public class MainActivity extends Activity { 8 | 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/apk/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Settings 4 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/b/f/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.b.f; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | 6 | 7 | /** 8 | * Source: BuildConfigUtil 9 | */ 10 | public class a { 11 | public final boolean i(boolean z) { // TODO: __REPLACE_METHOD 12 | return Hooks.isDevModeOn(); 13 | } 14 | 15 | public final boolean e() { // TODO: __REPLACE_METHOD 16 | return Hooks.isDevModeOn(); 17 | } 18 | 19 | public final boolean f() { // TODO: __REPLACE_METHOD 20 | return Hooks.isDevModeOn(); 21 | } 22 | 23 | public final boolean g() { // TODO: __REPLACE_METHOD 24 | return Hooks.isDevModeOn(); 25 | } 26 | 27 | public final boolean h() { // TODO: __REPLACE_METHOD 28 | return Hooks.isDevModeOn(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/e/g/f.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.e.g; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: FollowedGamesFetcher 7 | */ 8 | 9 | public class f extends tv.twitch.a.b.i.e { 10 | public final boolean x() { // TODO: __INJECT_METHOD 11 | return Hooks.hookFollowerFetcher(super.x()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/e/g/t.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.e.g; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: RecommendedStreamsFetcher 7 | */ 8 | public class t extends tv.twitch.a.b.i.e { 9 | public final boolean x() { // TODO: __INJECT_METHOD 10 | return Hooks.hookRecommendedFetcher(super.x()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/e/g/v.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.e.g; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: ResumeWatchingVideosFetcher 7 | */ 8 | public class v extends tv.twitch.a.b.i.e { 9 | public final boolean x() { // TODO: __INJECT_METHOD 10 | return Hooks.hookResumeWatchingFetcher(super.x()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/e/l/c.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.e.l; 2 | 3 | import tv.twitch.android.api.p1.i1; 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | import tv.twitch.android.models.Playable; 6 | 7 | /** 8 | * Source: ModelTheatreModeTracker 9 | */ 10 | public class c { 11 | public c(i1 i1Var, Playable playable, Object pageViewTracker) { 12 | Hooks.requestEmotes(i1Var, playable); // TODO: __INJECT_CALL 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/f/g.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.f; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | 6 | /** 7 | * Source: OkHttpClientFactory 8 | */ 9 | public class g { 10 | public g(Object gVar, Object fVar, Object sVar) { 11 | if (Hooks.isInterceptorOn()) { 12 | ; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/j/u/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.j.u; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | 6 | 7 | /** 8 | * Source: SearchSuggestionAdapterBinder 9 | */ 10 | public class a { 11 | 12 | public final void a(Object obj) { 13 | // "content" 14 | // "recentSearchSuggestionsKey" 15 | 16 | if (!Hooks.isJumpDisRecentSearch()) {} // TODO: __JUMP 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/e0/b/r/h.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.e0.b.r; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import android.graphics.drawable.Drawable; 6 | 7 | import com.bumptech.glide.load.n.g.c; 8 | 9 | import tv.twitch.android.mod.bridges.Hooks; 10 | import tv.twitch.android.mod.bridges.IDrawable; 11 | 12 | 13 | /** 14 | * Source: UrlDrawable 15 | */ 16 | public class h extends BitmapDrawable implements IDrawable { // TODO: __IMPLEMENT 17 | public Drawable a; 18 | private String b; 19 | public d c; 20 | private boolean isBits = false; // TODO: __ADD_FIELD 21 | 22 | 23 | /** 24 | * @param str Url 25 | * @param dVar MediaSpan 26 | */ 27 | public h(String str, d dVar) { 28 | this.b = str; 29 | this.c = dVar; 30 | } 31 | 32 | /** 33 | * @param drawable emote 34 | */ 35 | public final void c(Drawable drawable) { 36 | this.a = drawable; 37 | } 38 | 39 | @Override 40 | public void draw(Canvas canvas) { // TODO: __REPLACE_METHOD 41 | Drawable drawable = this.a; 42 | if (drawable != null) { 43 | drawable.draw(canvas); 44 | if (drawable instanceof com.bumptech.glide.load.n.g.c) { 45 | if (isBits) 46 | ((c) drawable).start(); 47 | else { 48 | if (Hooks.isGifsEnabled()) 49 | ((c) drawable).start(); 50 | } 51 | } 52 | } 53 | } 54 | 55 | public h(String str, d dVar, int i2, Object gVar) { 56 | isBits = i2 == 2; // TODO: __INJECT_CODE 57 | // this; 58 | } 59 | 60 | @Override 61 | public Drawable getDrawable() { 62 | return a; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/a0.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import tv.twitch.android.mod.bridges.Hooks; 8 | import tv.twitch.chat.ChatLiveMessage; 9 | import tv.twitch.chat.ChatMessageInfo; 10 | 11 | /** 12 | * Source: LiveChatSource 13 | */ 14 | public class a0 { 15 | public final void r(int i2, List list) { 16 | String u = ""; 17 | list = Hooks.hookLiveMessages(list, u); // TODO: __HOOK 18 | 19 | // move-object v0, p0 20 | // move-object v10, p2 21 | // iget-object v1, v0, Ltv/twitch/a/k/g/a0;->n:Ltv/twitch/a/b/n/a; 22 | // invoke-virtual {v1}, Ltv/twitch/a/b/n/a;->y()Ljava/lang/String; 23 | // move-result-object v1 24 | // invoke-static {v10, v1}, Ltv/twitch/android/mod/bridges/Hooks;->hookLiveMessages(Ljava/util/List;Ljava/lang/String;)Ljava/util/List; 25 | // move-result-object p2 26 | // move-object v10, p2 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/i1/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.i1; 2 | 3 | import android.text.SpannedString; 4 | 5 | import tv.twitch.a.k.g.g; 6 | import tv.twitch.a.k.e0.b.r.d; 7 | import tv.twitch.a.k.g.n0; 8 | import tv.twitch.android.mod.bridges.Hooks; 9 | import tv.twitch.android.mod.bridges.IChatMessageFactory; 10 | 11 | 12 | /** 13 | * Source: ChatMessageFactory 14 | */ 15 | public class a implements IChatMessageFactory { // TODO: __IMPLEMENT 16 | static CharSequence F(a factory, String url, tv.twitch.a.k.e0.b.r.d mediaSpan, String text, n0 urlImageClickableProvider, boolean z, int i2, Object obj) { 17 | return null; 18 | } 19 | 20 | private final CharSequence Q(tv.twitch.a.k.g.g gVar, int color, Object aVar, boolean z, String str, String str2) { 21 | color = Hooks.hookUsernameSpanColor(color); // TODO: __HOOK_PARAM 22 | 23 | return null; 24 | } 25 | 26 | public enum c { 27 | b, // CHAT_MESSAGE 28 | c, // ACTION 29 | d, // SYSTEM_MESSAGE 30 | e // HIGHLIGHTED_MESSAGE 31 | } 32 | 33 | private final e j(g chatMessageInterface, boolean z, boolean z2, boolean z3, int i2, int channelId, Object iClickableUsernameSpanListener, Object twitchUrlSpanClickListener, Object webViewSource, String str, boolean z4, Object censoredMessageTrackingInfo, Integer num, Object eventDispatcher) { 34 | try { 35 | // TODO: HOOK EXAMPLE 36 | // invoke-static/range {v6 .. v20}, Ltv/twitch/a/k/g/i1/a;->I(Ltv/twitch/a/k/g/i1/a;Ltv/twitch/a/k/g/g;ZZZLtv/twitch/android/models/webview/WebViewSource;Ltv/twitch/a/k/e0/b/r/g;Ljava/lang/String;Ltv/twitch/a/k/g/i1/a$c;ILtv/twitch/a/k/g/t1/c;Ljava/lang/Integer;Ltv/twitch/android/core/mvp/viewdelegate/EventDispatcher;ILjava/lang/Object;)Landroid/text/SpannedString; 37 | // move-result-object v8 38 | // move-object/from16 v7, p1 39 | // move/from16 v9, p6 40 | // invoke-static {v6, v7, v8, v9}, Ltv/twitch/android/mod/bridges/Hooks;->hookChatMessage(Ltv/twitch/android/mod/bridges/IChatMessageFactory;Ltv/twitch/a/k/g/g;Landroid/text/SpannedString;I)Landroid/text/SpannedString; 41 | // move-result-object v8 42 | 43 | SpannedString message = new SpannedString("KEKW"); 44 | message = Hooks.hookChatMessage(this, chatMessageInterface, message, channelId); 45 | 46 | return null; 47 | } catch (Throwable th) { 48 | th.printStackTrace(); 49 | 50 | return e.d; 51 | } 52 | } 53 | 54 | @Override 55 | public CharSequence getSpannedEmote(String url, String emoteText) { // TODO: __INJECT_METHOD 56 | return F(this, url, d.c, emoteText, null, false, 0, null); 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/l1/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.l1; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | import tv.twitch.android.models.channel.ChannelInfo; 6 | 7 | /** 8 | * Source: ChatConnectionController 9 | */ 10 | public class a { 11 | private final void n2(ChannelInfo channelInfo) { 12 | Hooks.requestEmotes(channelInfo); // TODO: __INJECT_CODE 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/q0/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.q0; 2 | 3 | import java.util.List; 4 | 5 | import tv.twitch.android.core.adapters.d0; 6 | import tv.twitch.android.core.adapters.t; 7 | import tv.twitch.android.mod.utils.GifHelper; 8 | 9 | /** 10 | * Source: ChannelChatAdapter 11 | */ 12 | public class a extends d0 { 13 | public void g() { 14 | GifHelper.recycleAdapterItems(U()); // TODO: __INJECT_CODE 15 | } 16 | 17 | public void R(List list) { 18 | int size = 0; 19 | if (size > 0) { 20 | GifHelper.recycleAdapterItems(U(), size); // TODO: __INJECT_CODE 21 | for (int i2 = 0; i2 < size; i2++) { 22 | U().remove(0); 23 | } 24 | // d(0, size); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/v1/c.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.v1; 2 | 3 | import android.content.Context; 4 | 5 | import tv.twitch.chat.ChatEmoticon; 6 | 7 | /** 8 | * Source: ChatEmoticonUtils 9 | */ 10 | public final class c { 11 | public static final String c(Context context, ChatEmoticon chatEmoticon) { // TODO: __INJECT_METHOD 12 | if (chatEmoticon.url != null) 13 | return chatEmoticon.url; 14 | 15 | return org(context, chatEmoticon); 16 | } 17 | 18 | public static final String org(Context context, ChatEmoticon chatEmoticon) { // TODO: __RENAME__c 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/g/x0/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.x0; 2 | 3 | 4 | import android.text.Spanned; 5 | import android.widget.TextView; 6 | 7 | import tv.twitch.android.mod.bridges.IMessageRecyclerItem; 8 | 9 | /** 10 | * Source: ChommentRecyclerItem 11 | */ 12 | public class d implements IMessageRecyclerItem { // TODO: __IMPLEMENT 13 | private Spanned c; 14 | 15 | @Override 16 | public Spanned getSpanned() { // TODO: __INJECT_METHOD 17 | return c; 18 | } 19 | 20 | public static final class b implements IMessageRecyclerItem { // TODO: __IMPLEMENT 21 | private TextView u; 22 | 23 | @Override 24 | public Spanned getSpanned() { // TODO: __INJECT_METHOD 25 | return (Spanned) u.getText(); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/m/e.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.m; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: ExperimentHelper 7 | */ 8 | public class e { 9 | public boolean I(a aVar) { // TODO: __INEJECT_METHOD 10 | return Hooks.hookExperimental(aVar, org(aVar)); 11 | } 12 | 13 | 14 | public boolean org(a aVar) { // TODO: __RENAME__a 15 | return false; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/a/k/x/v.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.x; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: VideoDebugConfig 7 | */ 8 | public class v { 9 | public final boolean org() { // TODO: __RENAME__a 10 | return false; 11 | } 12 | 13 | public final boolean a() { // TODO: __INJECT_METHOD 14 | return Hooks.hookVideoDebugPanel(org()); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/adapters/a/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.adapters.a; 2 | 3 | import android.content.Context; 4 | import android.text.Spanned; 5 | import android.view.MotionEvent; 6 | import android.widget.TextView; 7 | 8 | import tv.twitch.android.mod.bridges.Hooks; 9 | import tv.twitch.android.mod.bridges.IMessageRecyclerItem; 10 | 11 | /** 12 | * Source: MessageRecyclerItem 13 | */ 14 | public class b implements IMessageRecyclerItem { // TODO: __IMPLEMENT 15 | private boolean j; 16 | private Spanned f; 17 | 18 | public String l; // chat msg 19 | 20 | 21 | public b(Context context, String messageId, int i2, String str2, String str3, int i3, Spanned message, Object gVar, float f2, int i4, float f3, boolean z, boolean z2, String str4, Object eventDispatcher) { 22 | message = Hooks.addTimestampToMessage(message, messageId); // TODO: __HOOK_PARAM 23 | } 24 | 25 | public void j() { 26 | this.j = true; // TODO: __INJECT_CODE 27 | } 28 | 29 | @Override 30 | public Spanned getSpanned() { // TODO: __INJECT_METHOD 31 | return f; 32 | } 33 | 34 | public static final class a implements IMessageRecyclerItem { // TODO: __IMPLEMENT 35 | public final TextView Q() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public Spanned getSpanned() { // TODO: __INJECT_METHOD 41 | return (Spanned) Q().getText(); 42 | } 43 | } 44 | 45 | static final class c { 46 | final b c = null; 47 | 48 | public final boolean d(MotionEvent motionEvent) { 49 | Hooks.tryCopyMsg(c.l); // TODO: __INJECT_CODE 50 | return true; 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/app/core/j2/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.app.core.j2; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import tv.twitch.android.mod.bridges.Hooks; 7 | 8 | 9 | /** 10 | * Source: BottomNavigationPresenter 11 | */ 12 | public class b { 13 | private List b; 14 | 15 | private enum a { 16 | e, // ic_navigation_following_selector 17 | f, // ic_navigation_discover_selector 18 | g, // ic_navigation_browse_selector 19 | h, // ic_navigation_esports_selector 20 | i // ic_navigation_broadcast 21 | } 22 | 23 | // constructor 24 | public b() { 25 | this.b = filterList(null); // TODO: __INJECT_CODE 26 | } 27 | 28 | private static List filterList(ArrayList list) { // TODO: __INJECT_METHOD 29 | if (list == null) { 30 | return null; 31 | } 32 | 33 | if (Hooks.isHideDiscoverTab()) { 34 | list.remove(a.f); 35 | } 36 | if (Hooks.isHideEsportsTab()) { 37 | list.remove(a.h); 38 | } 39 | 40 | return list; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/core/adapters/d0.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.core.adapters; 2 | 3 | 4 | import java.util.List; 5 | 6 | import tv.twitch.android.mod.utils.GifHelper; 7 | 8 | /** 9 | * Source: TwitchAdapter 10 | */ 11 | public class d0 { 12 | private List d; 13 | 14 | protected final List U() { 15 | return this.d; 16 | } 17 | 18 | 19 | public void J(Object b0Var) { 20 | if (b0Var instanceof Object) { 21 | GifHelper.recycleObject(b0Var, false); // TODO: __INJECT_CODE 22 | } 23 | } 24 | 25 | public final void S() { 26 | GifHelper.recycleAdapterItems(this.d); // TODO: __INJECT_CODE 27 | } 28 | 29 | public void R(List list) {} 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/core/crashreporter/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.core.crashreporter; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | /** 7 | * Source: CrashReporter 8 | */ 9 | public class a { 10 | private void test() { 11 | Log("KEKW"); 12 | Log("KEKW", "LULW"); 13 | Log(0, "KEKW", "LULW"); 14 | Log(new Exception()); 15 | Log("KEKW", true); 16 | 17 | } 18 | private static void Log(String what) { 19 | if (TextUtils.isEmpty(what)) { 20 | return; 21 | } 22 | Log.d("CRASHLYTICS", "what==" + what); 23 | } 24 | 25 | private static void Log(String tag, String what) { 26 | if (TextUtils.isEmpty(what)) { 27 | return; 28 | } 29 | if (TextUtils.isEmpty(tag)) { 30 | Log.d("CRASHLYTICS", "tag==null"); 31 | return; 32 | } 33 | Log.d("CRASHLYTICS", "tag==" + tag + ", what==" + what); 34 | } 35 | 36 | private static void Log(String what, boolean z) { 37 | if (TextUtils.isEmpty(what)) { 38 | return; 39 | } 40 | Log.d("CRASHLYTICS", "what==" + what + ", z=" + z); 41 | } 42 | 43 | private static void Log(Throwable throwable) { 44 | if (throwable == null) { 45 | return; 46 | } 47 | Log.d("CRASHLYTICS", "th==" + throwable.toString()); 48 | } 49 | 50 | private static void Log(int i, String tag, String what) { 51 | if (TextUtils.isEmpty(what)) { 52 | return; 53 | } 54 | if (TextUtils.isEmpty(tag)) { 55 | return; 56 | } 57 | Log.d("CRASHLYTICS", "i==" + i + ", tag==" + tag + ", what==" + what); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/feature/theatre/common/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.feature.theatre.common; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | 6 | /** 7 | * Source: FloatingChatPresenter 8 | */ 9 | public class d { 10 | public final boolean org() { // TODO: __RENAME__h2 11 | return false; 12 | } 13 | 14 | public final boolean h2() { // TODO: __REPLACE_METHOD 15 | if (Hooks.isFloatingChatEnabled()) 16 | return true; 17 | 18 | return org(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/feature/theatre/common/i.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.feature.theatre.common; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: MiniPlayerSize 7 | */ 8 | public class i { 9 | 10 | // Source: b() 11 | public final int org() { // TODO: __RENAME__b 12 | return 0; 13 | } 14 | 15 | public final int b() { // TODO: __INJECT_METHOD 16 | return Hooks.hookMiniplayerSize(org()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/models/player/PlayerImplementation.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models.player; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | 6 | 7 | public enum PlayerImplementation { 8 | Core("playercore", "c"), 9 | Exo2("exoplayer_2", "e2"); 10 | 11 | PlayerImplementation(String str, String str2) { 12 | } 13 | 14 | public static final class Companion { 15 | public final PlayerImplementation getProviderForName(String str) { 16 | str = Hooks.hookPlayerProvider(str); // TODO: __HOOK_PARAM 17 | 18 | return null; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/player/autoplayoverlay/RecommendationAutoPlayPresenter.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.player.autoplayoverlay; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | import tv.twitch.android.models.Playable; 5 | 6 | public class RecommendationAutoPlayPresenter { 7 | public final void prepareRecommendationForCurrentModel(T t) { 8 | // resetAutoplay(); 9 | 10 | if (!Hooks.isJumpDisableAutoplay()) // TODO: __JUMP 11 | // ISubscriptionHelper.... 12 | return; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/sdk/z.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.sdk; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | import tv.twitch.chat.ChatEmoticonSet; 5 | 6 | /** 7 | * Source: ChatController 8 | */ 9 | public class z { 10 | public ChatEmoticonSet[] k = null; // widget set 11 | 12 | public ChatEmoticonSet[] Q() { // TODO: __REPLACE_METHOD 13 | return getK(); 14 | } 15 | 16 | // TODO: __REPLACE_DIRECT_CALL 17 | public ChatEmoticonSet[] getK() { // TODO: __INJECT_METHOD 18 | return Hooks.hookChatEmoticonSet(this.k); 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/settings/z/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.settings.z; 2 | 3 | 4 | import tv.twitch.android.mod.settings.SettingsController; 5 | import tv.twitch.android.shared.ui.menus.j; 6 | import tv.twitch.android.shared.ui.menus.s.b; 7 | 8 | 9 | /** 10 | * Source: SystemSettingsPresenter 11 | */ 12 | public class d extends tv.twitch.android.settings.l.b { 13 | public static final class ToggleMenuChangeListener implements j { // TODO: __INJECT_CLASS 14 | @Override 15 | public void a(tv.twitch.android.shared.ui.menus.k.b bVar) {} 16 | 17 | @Override 18 | public void a(b item, boolean isChecked) { 19 | SettingsController.OnToggleEvent(item, isChecked); 20 | } 21 | } 22 | 23 | 24 | @Override 25 | protected tv.twitch.android.settings.l.d U1() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public String X1() { 31 | return null; 32 | } 33 | 34 | @Override 35 | protected j V1() { // TODO: __REPLACE_METHOD 36 | return new ToggleMenuChangeListener(); 37 | } 38 | 39 | @Override 40 | public void c2() { // TODO: __REPLACE_METHOD 41 | SettingsController.initialize(R1(), W1()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/shared/chat/communitypoints/t.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.chat.communitypoints; 2 | 3 | import android.os.Handler; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.Toast; 7 | 8 | import java.util.HashMap; 9 | import java.util.Random; 10 | 11 | import tv.twitch.android.mod.bridges.Hooks; 12 | import tv.twitch.android.mod.utils.Clicker; 13 | import tv.twitch.android.mod.utils.Helper; 14 | import tv.twitch.android.shared.chat.communitypoints.models.CommunityPointsModel; 15 | 16 | 17 | /** 18 | * Source: CommunityPointsButtonViewDelegate 19 | */ 20 | public class t { 21 | private final ViewGroup b = null; 22 | 23 | 24 | private final void E(CommunityPointsModel communityPointsModel) { // TODO: __REPLACE_METHOD 25 | Hooks.setClicker(new f(this, communityPointsModel), communityPointsModel); 26 | } 27 | 28 | static final class f implements View.OnClickListener { 29 | f(t tVar, CommunityPointsModel communityPointsModel) {} 30 | 31 | public final void onClick(View view) {} 32 | } 33 | 34 | private void z() { 35 | // TODO: __REMOVE_TOAST 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/shared/chat/messageinput/h.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.chat.messageinput; 2 | 3 | import tv.twitch.a.k.g.d1.b; 4 | import tv.twitch.android.mod.bridges.Hooks; 5 | 6 | /** 7 | * Source: ChatMessageInputViewPresenter 8 | */ 9 | public class h { 10 | static final class f { 11 | public final void a(b bVar) { 12 | Hooks.setCurrentChannel(bVar); // TODO: __INJECT_METHOD_CALL 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/tv/twitch/android/shared/chat/messageinput/u/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.chat.messageinput.u; 2 | 3 | import tv.twitch.android.mod.bridges.Hooks; 4 | 5 | /** 6 | * Source: EmoteAdapterSection 7 | */ 8 | public class d { 9 | private String e = ""; 10 | 11 | 12 | private final String w() { 13 | return null; 14 | } 15 | 16 | public void e(Object b0Var) { 17 | // invoke-direct {p0}, Ltv/twitch/android/shared/chat/messageinput/u/d;->w()Ljava/lang/String; 18 | // move-result-object v0 19 | // invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; 20 | // const-string v1, "" 21 | // invoke-static {v0, v1}, Ltv/twitch/android/mod/bridges/Hooks;->hookSetName(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 22 | // move-result-object v0 23 | 24 | String str = Hooks.hookSetName(w(), this.e); // TODO: __HOOK 25 | } 26 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | //noinspection GradleDependency 11 | classpath 'com.android.tools.build:gradle:4.0.0' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | 17 | 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrozenAlex/TwitchMod/171f533bf96edc47e491beefa9d50d4ea7d2c059/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jun 13 10:26:53 MSK 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 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 | -------------------------------------------------------------------------------- /mod/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mod/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode 22 10 | versionName "2.1.1" 11 | 12 | consumerProguardFiles 'consumer-rules.pro' 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | buildToolsVersion = '29.0.3' 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | implementation 'com.google.code.gson:gson:2.8.6' 27 | //noinspection GradleDependency 28 | implementation 'com.squareup.retrofit2:retrofit:2.6.1' 29 | //noinspection GradleDependency 30 | implementation 'com.squareup.retrofit2:converter-gson:2.6.1' 31 | implementation project(path: ':twitch') 32 | } 33 | -------------------------------------------------------------------------------- /mod/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrozenAlex/TwitchMod/171f533bf96edc47e491beefa9d50d4ea7d2c059/mod/consumer-rules.pro -------------------------------------------------------------------------------- /mod/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /mod/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/ApiCallback.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import retrofit2.Call; 5 | import retrofit2.Callback; 6 | import retrofit2.Response; 7 | import tv.twitch.android.mod.models.api.FailReason; 8 | import tv.twitch.android.mod.utils.Logger; 9 | 10 | 11 | public abstract class ApiCallback implements Callback { 12 | private static final int MAX_RETRIES = 3; 13 | 14 | private int retryCount = 0; 15 | 16 | 17 | public void onFailure(Call call, Throwable callThrowable) { 18 | Logger.debug("retryCount=" + retryCount); 19 | 20 | if (retryCount++ < MAX_RETRIES) { 21 | Logger.debug("Next try..."); 22 | try { 23 | retry(call); 24 | return; 25 | } catch (Throwable cloneThrowable) { 26 | cloneThrowable.printStackTrace(); 27 | } 28 | } 29 | 30 | retryCount = 0; 31 | callThrowable.printStackTrace(); 32 | onRequestFail(call, FailReason.EXCEPTION); 33 | } 34 | 35 | protected final void retry(Call call) { 36 | call.clone().enqueue(this); 37 | } 38 | 39 | public abstract void onRequestSuccess(T t); 40 | 41 | public abstract void onRequestFail(Call call, FailReason failReason); 42 | 43 | public void onResponse(Call call, Response response) { 44 | if (response.code() == 404) { 45 | onRequestFail(call, FailReason.NOT_FOUND); 46 | return; 47 | } 48 | 49 | if (!response.isSuccessful()) { 50 | onRequestFail(call, FailReason.UNSUCCESSFUL); 51 | return; 52 | } 53 | 54 | if (response.body() == null) { 55 | onRequestFail(call, FailReason.NULL_BODY); 56 | return; 57 | } 58 | 59 | onRequestSuccess(response.body()); 60 | } 61 | 62 | public abstract void fetch(); 63 | } 64 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/ChatFactory.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | import java.util.Collection; 4 | 5 | import tv.twitch.android.mod.models.Emote; 6 | import tv.twitch.chat.ChatEmoticon; 7 | import tv.twitch.chat.ChatEmoticonSet; 8 | 9 | public class ChatFactory { 10 | private static final int MASK = 0xFFFF0000; 11 | 12 | private static int lastEmoteId = Integer.MAX_VALUE; 13 | 14 | private static synchronized String generateEmoteId() { 15 | if (lastEmoteId == Integer.MAX_VALUE) 16 | lastEmoteId = lastEmoteId & MASK; 17 | 18 | return String.valueOf(lastEmoteId++); 19 | } 20 | 21 | public static ChatEmoticon getEmoticon(String url, String code) { 22 | ChatEmoticon chatEmoticon = new ChatEmoticon(); 23 | chatEmoticon.match = code; 24 | chatEmoticon.isRegex = false; 25 | chatEmoticon.emoticonId = generateEmoteId(); 26 | chatEmoticon.url = url; 27 | 28 | return chatEmoticon; 29 | } 30 | 31 | public static ChatEmoticonSet getSet(String setId, Collection emotes) { 32 | ChatEmoticonSet set = new ChatEmoticonSet(); 33 | set.emoticonSetId = setId; 34 | set.emoticons = convert(emotes); 35 | 36 | return set; 37 | } 38 | 39 | private static ChatEmoticon[] convert(Collection emoteList) { 40 | if (emoteList == null || emoteList.size() == 0) 41 | return new ChatEmoticon[0]; 42 | 43 | ChatEmoticon[] chatEmoticons = new ChatEmoticon[emoteList.size()]; 44 | 45 | int i = 0; 46 | for (Emote emote : emoteList) { 47 | chatEmoticons[i++] = emote.getChatEmoticon(); 48 | } 49 | 50 | return chatEmoticons; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/Hooks.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.text.Spanned; 5 | import android.text.SpannedString; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.google.android.exoplayer2.PlaybackParameters; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | import tv.twitch.a.k.g.d1.b; 18 | import tv.twitch.a.k.g.g; 19 | import tv.twitch.android.api.p1.i1; 20 | import tv.twitch.android.mod.emotes.EmoteManager; 21 | import tv.twitch.android.mod.models.Emote; 22 | import tv.twitch.android.mod.models.settings.UserMessagesFiltering; 23 | import tv.twitch.android.mod.models.settings.Gifs; 24 | import tv.twitch.android.mod.models.settings.PlayerImpl; 25 | import tv.twitch.android.mod.settings.PrefManager; 26 | import tv.twitch.android.mod.utils.ChatFilteringUtil; 27 | import tv.twitch.android.mod.utils.ChatUtil; 28 | import tv.twitch.android.mod.utils.Helper; 29 | import tv.twitch.android.mod.utils.Logger; 30 | import tv.twitch.android.models.Playable; 31 | import tv.twitch.android.models.channel.ChannelInfo; 32 | import tv.twitch.android.shared.chat.communitypoints.models.CommunityPointsModel; 33 | import tv.twitch.chat.ChatEmoticonSet; 34 | import tv.twitch.chat.ChatLiveMessage; 35 | 36 | 37 | @SuppressWarnings("FinalStaticMethod") 38 | public class Hooks { 39 | private final static String VOD_PLAYER_PRESENTER_CLASS = "tv.twitch.a.k.w.j0.m"; 40 | private final static float DEFAULT_MINIPLAYER_SIZE = 1.0f; 41 | 42 | /** 43 | * Class: tv.twitch.android.adapters.a.b.c 44 | * signature: public final boolean a(MotionEvent motionEvent) 45 | */ 46 | public final static void tryCopyMsg(String msg) { 47 | if (!LoaderLS.getPrefManagerInstance().isCopyMsgOn()) 48 | return; 49 | 50 | if (TextUtils.isEmpty(msg)) { 51 | Logger.debug("empty msg"); 52 | return; 53 | } 54 | 55 | Helper.saveToClipboard(msg); 56 | } 57 | 58 | /** 59 | * Class: EmoteAdapterSection 60 | * signature: public void a(RecyclerView.b0 b0Var) 61 | */ 62 | public final static String hookSetName(String org, String setId) { 63 | if (!LoaderLS.getPrefManagerInstance().isHookEmoticonSetOn()) 64 | return org; 65 | 66 | ChatUtil.EmoteSet set = ChatUtil.EmoteSet.findById(setId); 67 | if (set != null) 68 | return set.getDescription(); 69 | 70 | return org; 71 | } 72 | 73 | /** 74 | * Class: ExperimentHelper 75 | * signature: public boolean I(a aVar) 76 | */ 77 | public final static boolean hookExperimental(tv.twitch.a.k.m.a experimental, boolean org) { 78 | switch (experimental) { 79 | case G0: 80 | switch (LoaderLS.getPrefManagerInstance().getEmotePickerView()) { 81 | case AUTO: 82 | return org; 83 | case NEW: 84 | return true; 85 | case OLD: 86 | return false; 87 | } 88 | case m0: 89 | switch (LoaderLS.getPrefManagerInstance().getFollowView()) { 90 | case AUTO: 91 | return org; 92 | case NEW: 93 | return true; 94 | case OLD: 95 | return false; 96 | } 97 | } 98 | 99 | return org; 100 | } 101 | 102 | /** 103 | * Class: CommunityPointsButtonViewDelegate 104 | * signature: private final void e(CommunityPointsModel communityPointsModel) 105 | */ 106 | // TODO: REMOVE communityPointsModel 107 | public final static void setClicker(View.OnClickListener listener, CommunityPointsModel communityPointsModel) { 108 | LoaderLS.getHelperInstance().setClicker(listener); 109 | } 110 | 111 | /** 112 | * Class: StandaloneMediaClock 113 | * signatute: private PlaybackParameters f = PlaybackParameters.e; 114 | */ 115 | public final static PlaybackParameters hookVodPlayerStandaloneMediaClockInit(PlaybackParameters org) { 116 | PrefManager prefManager = LoaderLS.getPrefManagerInstance(); 117 | float speed = Float.parseFloat(prefManager.getExoplayerSpeed().getPreferenceValue()); 118 | if (speed == org.a) 119 | return org; 120 | 121 | if (Helper.checkStackTrace(VOD_PLAYER_PRESENTER_CLASS)) { 122 | return new PlaybackParameters(speed); 123 | } 124 | 125 | return PlaybackParameters.e; 126 | } 127 | 128 | /** 129 | * Class: MessageRecyclerItem 130 | * signature: 131 | */ 132 | public final static Spanned addTimestampToMessage(Spanned message, String messageId) { 133 | if (!LoaderLS.getPrefManagerInstance().isTimestampsOn()) 134 | return message; 135 | 136 | return ChatUtil.addTimestamp(message, new Date()); 137 | } 138 | 139 | /** 140 | * Class: ChatController 141 | * signature: ChatEmoticonSet[] b() 142 | */ 143 | public final static ChatEmoticonSet[] hookChatEmoticonSet(ChatEmoticonSet[] orgSet) { 144 | if (!LoaderLS.getPrefManagerInstance().isHookEmoticonSetOn()) 145 | return orgSet; 146 | 147 | if (orgSet == null) 148 | return null; 149 | 150 | Helper helper = LoaderLS.getHelperInstance(); 151 | EmoteManager emoteManager = LoaderLS.getEmoteMangerInstance(); 152 | 153 | final int currentChannel = helper.getCurrentChannel(); 154 | Collection globalEmotes = emoteManager.getGlobalEmotes(); 155 | Collection bttvEmotes = emoteManager.getBttvEmotes(currentChannel); 156 | Collection ffzEmotes = emoteManager.getFfzEmotes(currentChannel); 157 | 158 | ChatEmoticonSet[] newSet = new ChatEmoticonSet[orgSet.length+3]; 159 | System.arraycopy(orgSet, 0, newSet, 0, orgSet.length); 160 | 161 | newSet[newSet.length-1] = ChatFactory.getSet(ChatUtil.EmoteSet.BTTV.getId(), bttvEmotes); 162 | newSet[newSet.length-2] = ChatFactory.getSet(ChatUtil.EmoteSet.FFZ.getId(), ffzEmotes); 163 | newSet[newSet.length-3] = ChatFactory.getSet(ChatUtil.EmoteSet.GLOBAL.getId(), globalEmotes); 164 | 165 | return newSet; 166 | } 167 | 168 | /** 169 | * Class: ChatConnectionController 170 | * signature: private final void a(ChannelInfo channelInfo) 171 | */ 172 | public final static void requestEmotes(ChannelInfo channelInfo) { 173 | if (!LoaderLS.getPrefManagerInstance().isEmotesOn()) 174 | return; 175 | 176 | LoaderLS.getEmoteMangerInstance().requestEmotes(channelInfo.getId(), false); 177 | } 178 | 179 | /** 180 | * Class: ModelTheatreModeTracker 181 | * signature: public c(f1 f1Var, Playable playable, Object pageViewTracker) 182 | */ 183 | public final static void requestEmotes(final i1 playableModelParser, final Playable playable) { 184 | if (!LoaderLS.getPrefManagerInstance().isEmotesOn()) 185 | return; 186 | 187 | LoaderLS.getEmoteMangerInstance().requestEmotes(Helper.getChannelId(playableModelParser, playable), true); 188 | } 189 | 190 | /** 191 | * Class: FollowedGamesFetcher 192 | * signature: public final boolean j() 193 | */ 194 | public final static boolean hookFollowerFetcher(boolean org) { 195 | if (!LoaderLS.getPrefManagerInstance().isDisableFollowedGames()) 196 | return org; 197 | 198 | return false; 199 | } 200 | 201 | /** 202 | * Class: RecommendedStreamsFetcher 203 | * signature: public final boolean j() 204 | */ 205 | public final static boolean hookRecommendedFetcher(boolean org) { 206 | if (!LoaderLS.getPrefManagerInstance().isDisableRecommendations()) 207 | return org; 208 | 209 | return false; 210 | } 211 | 212 | /** 213 | * Class: ResumeWatchingVideosFetcher 214 | * signature: public final boolean j() 215 | */ 216 | public final static boolean hookResumeWatchingFetcher(boolean org) { 217 | if (!LoaderLS.getPrefManagerInstance().isDisableRecentWatching()) 218 | return org; 219 | 220 | return false; 221 | } 222 | 223 | /** 224 | * Class: VideoDebugConfig 225 | * signature: public final boolean a() 226 | */ 227 | public final static boolean hookVideoDebugPanel(boolean org) { 228 | if (!LoaderLS.getPrefManagerInstance().isShowVideoDebugPanel()) 229 | return org; 230 | 231 | return true; 232 | } 233 | 234 | /** 235 | * Class: MiniPlayerSize 236 | * signature: public final int b() 237 | */ 238 | public final static int hookMiniplayerSize(int size) { 239 | PrefManager prefManager = LoaderLS.getPrefManagerInstance(); 240 | float k = Float.parseFloat(prefManager.getMiniPlayerSize().getPreferenceValue()); 241 | if (k == DEFAULT_MINIPLAYER_SIZE) 242 | return size; 243 | 244 | return (int) (k * size); 245 | } 246 | 247 | /** 248 | * Class: PlayerImplementation 249 | * signature: public final PlayerImplementation getProviderForName(String str) 250 | */ 251 | public final static String hookPlayerProvider(String name) { 252 | if (TextUtils.isEmpty(name)) { 253 | Logger.warning("empty name"); 254 | return name; 255 | } 256 | 257 | PlayerImpl playerImpl = LoaderLS.getPrefManagerInstance().getPlayer(); 258 | switch (playerImpl) { 259 | default: 260 | case AUTO: 261 | return name; 262 | case CORE: 263 | case EXO: 264 | return playerImpl.getPreferenceValue(); 265 | } 266 | } 267 | 268 | /** 269 | * Class: SearchSuggestionAdapterBinder 270 | * signature: public final void a(Object obj) 271 | */ 272 | public final static boolean isJumpDisRecentSearch() { 273 | return LoaderLS.getPrefManagerInstance().isDisableRecentSearch(); 274 | } 275 | 276 | /** 277 | * Class: *.* 278 | */ 279 | public final static boolean isDevModeOn() { 280 | return LoaderLS.getPrefManagerInstance().isDevModeOn(); 281 | } 282 | 283 | /** 284 | * Class: *.* 285 | */ 286 | public final static boolean isInterceptorOn() { 287 | return LoaderLS.getPrefManagerInstance().isInterceptorOn(); 288 | } 289 | 290 | /** 291 | * Class: tv.twitch.a.k.g.a0 292 | * signature: public final void a(int i2, List list...) 293 | */ 294 | public final static List hookLiveMessages(List list, String accountName) { 295 | UserMessagesFiltering filtering = LoaderLS.getPrefManagerInstance().getChatFiltering(); 296 | if (filtering == UserMessagesFiltering.DISABLED) 297 | return list; 298 | 299 | if (TextUtils.isEmpty(accountName)) { 300 | Logger.error("empty accountName"); 301 | return list; 302 | } 303 | 304 | if (list == null || list.isEmpty()) { 305 | Logger.warning("empty list"); 306 | return list; 307 | } 308 | 309 | ArrayList filtered = new ArrayList<>(); 310 | for (ChatLiveMessage liveMessage : list) { 311 | if (liveMessage == null || liveMessage.messageInfo == null) { 312 | filtered.add(liveMessage); 313 | continue; 314 | } 315 | 316 | if (ChatFilteringUtil.filter(liveMessage, accountName, filtering)) 317 | filtered.add(liveMessage); 318 | } 319 | 320 | return filtered; 321 | } 322 | 323 | /** 324 | * Class: RecommendationAutoPlayPresenter 325 | * signature: public final void prepareRecommendationForCurrentModel(T t) 326 | */ 327 | public final static boolean isJumpDisableAutoplay() { 328 | return LoaderLS.getPrefManagerInstance().isDisableAutoplay(); 329 | } 330 | 331 | /** 332 | * Class: ChatMessageInputViewPresenter 333 | * signature: public final void a(b bVar) 334 | */ 335 | public final static void setCurrentChannel(b event) { 336 | if (event == null) { 337 | Logger.error("event is null"); 338 | return; 339 | } 340 | 341 | LoaderLS.getHelperInstance().setCurrentChannel(event.a().getId()); 342 | } 343 | 344 | /** 345 | * Class: *.* 346 | */ 347 | public final static boolean isHideDiscoverTab() { 348 | return LoaderLS.getPrefManagerInstance().isHideDiscoverTab(); 349 | } 350 | 351 | /** 352 | * Class: *.* 353 | */ 354 | public final static boolean isHideEsportsTab() { 355 | return LoaderLS.getPrefManagerInstance().isHideEsportsTab(); 356 | } 357 | 358 | /** 359 | * Class: *.* 360 | */ 361 | public final static boolean isFloatingChatEnabled() { 362 | return LoaderLS.getPrefManagerInstance().isFloatingChatEnabled(); 363 | } 364 | 365 | /** 366 | * Class: *.* 367 | */ 368 | public final static boolean isGifsEnabled() { 369 | return LoaderLS.getPrefManagerInstance().getGifsStrategy() == Gifs.ANIMATED; 370 | } 371 | 372 | /** 373 | * Some hooks 374 | */ 375 | public final static void helper() { 376 | Object o = hookVodPlayerStandaloneMediaClockInit(new PlaybackParameters(0.0f)); // TODO: __HOOK 377 | } 378 | 379 | /** 380 | * Class: ChatMessageFactory 381 | */ 382 | public final static int hookUsernameSpanColor(int usernameColor) { 383 | return ChatUtil.fixUsernameColor(usernameColor); 384 | } 385 | 386 | /** 387 | * Class: ChatMessageFactory 388 | */ 389 | public static SpannedString hookChatMessage(IChatMessageFactory factory, g chatMessageInterface, SpannedString orgMessage, int channelId) { 390 | PrefManager manager = LoaderLS.getPrefManagerInstance(); 391 | if (!manager.isEmotesOn()) 392 | return orgMessage; 393 | 394 | try { 395 | if (TextUtils.isEmpty(orgMessage)) 396 | return orgMessage; 397 | 398 | if (chatMessageInterface.a()) 399 | return orgMessage; 400 | 401 | return ChatUtil.injectEmotesSpan(factory, LoaderLS.getEmoteMangerInstance(), orgMessage, channelId, manager); 402 | } catch (Throwable th) { 403 | th.printStackTrace(); 404 | if (isDevModeOn()) { 405 | if (chatMessageInterface.e() == null) { 406 | Logger.showDebugToast("message is null"); 407 | return orgMessage; 408 | } 409 | 410 | String message = ChatUtil.getRawMessage(chatMessageInterface.e()); 411 | if (!TextUtils.isEmpty(message)) { 412 | Logger.showDebugToast("Bad message: '" + message + "'"); 413 | } else { 414 | Logger.showDebugToast("Empty message"); 415 | } 416 | } 417 | } 418 | 419 | return orgMessage; 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/IChatMessageFactory.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | public interface IChatMessageFactory { 5 | CharSequence getSpannedEmote(String url, String emoteText); 6 | } 7 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/IDPub.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.util.LruCache; 7 | 8 | 9 | public class IDPub { 10 | public static final int PLAYER_OVERLAY_ID = 0x7f0b0626; 11 | public static final int DEBUG_PANEL_CONTAINER_ID = 0x7f0b02be; 12 | public static final int FLOATING_CHAT_CONTAINER_ID = 0x7f0b03b2; 13 | public static final int VIDEO_DEBUG_LIST_ID = 0x7f0b08d7; 14 | public static final int MESSAGES_CONTAINER_ID = 0x7f0b04e3; 15 | 16 | public final PubCache mStringIdsCache = new PubCache(200, "string"); 17 | 18 | 19 | private static class PubCache extends LruCache { 20 | private final String mDefType; 21 | 22 | public PubCache(int maxSize, String defType) { 23 | super(maxSize); 24 | mDefType = defType; 25 | } 26 | 27 | @Override 28 | protected Integer create(String key) { 29 | Context context = LoaderLS.getInstance(); 30 | Resources resources = context.getResources(); 31 | return resources.getIdentifier(key, mDefType, context.getPackageName()); 32 | } 33 | } 34 | 35 | public String getString(String name) { 36 | int resId = mStringIdsCache.get(name); 37 | if (resId == 0) { 38 | return "RESOURCE ID NOT FOUND: '" + name + "'"; 39 | } else { 40 | return LoaderLS.getInstance().getResources().getString(resId); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/IDrawable.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.graphics.drawable.Drawable; 5 | 6 | 7 | public interface IDrawable { 8 | Drawable getDrawable(); 9 | } 10 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/IMessageRecyclerItem.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.text.Spanned; 5 | 6 | 7 | public interface IMessageRecyclerItem { 8 | Spanned getSpanned(); 9 | } 10 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/LoaderLS.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.content.Context; 5 | 6 | import tv.twitch.android.app.consumer.TwitchApplication; 7 | import tv.twitch.android.mod.emotes.EmoteManager; 8 | import tv.twitch.android.mod.settings.PrefManager; 9 | import tv.twitch.android.mod.utils.Helper; 10 | import tv.twitch.android.mod.utils.Logger; 11 | 12 | 13 | public class LoaderLS extends TwitchApplication { 14 | public static final String VERSION = "TwitchMod v2.2"; 15 | public static final String BUILD = "TEST BUILD r9"; 16 | 17 | private EmoteManager sEmoteManager; 18 | private PrefManager sPrefManager; 19 | private Helper sHelper; 20 | private IDPub sIDPub; 21 | 22 | private static volatile LoaderLS sInstance = null; 23 | 24 | public static LoaderLS getInstance() { 25 | return sInstance; 26 | } 27 | 28 | @Override 29 | protected void attachBaseContext(Context base) { 30 | super.attachBaseContext(base); 31 | } 32 | 33 | @Override 34 | public void onCreate() { 35 | init(); 36 | super.onCreate(); 37 | post(); 38 | } 39 | 40 | private void post() { } 41 | 42 | private void init() { 43 | Logger.debug("Init LoaderLS. " + VERSION); 44 | sInstance = this; 45 | sEmoteManager = new EmoteManager(); 46 | sPrefManager = new PrefManager(getApplicationContext()); 47 | sHelper = new Helper(); 48 | sIDPub = new IDPub(); 49 | } 50 | 51 | public static EmoteManager getEmoteMangerInstance() { 52 | return getInstance().getEmoteManager(); 53 | } 54 | 55 | public static PrefManager getPrefManagerInstance() { 56 | return getInstance().getPrefManager(); 57 | } 58 | 59 | public static Helper getHelperInstance() { 60 | return getInstance().getHelper(); 61 | } 62 | 63 | public static IDPub getIDPubInstance() { 64 | return getInstance().getIDPub(); 65 | } 66 | 67 | private EmoteManager getEmoteManager() { 68 | if (sEmoteManager == null) { 69 | synchronized (LoaderLS.class) { 70 | if (sEmoteManager == null) { 71 | Logger.warning("creating new instance"); 72 | sEmoteManager = new EmoteManager(); 73 | } 74 | } 75 | } 76 | return sEmoteManager; 77 | } 78 | 79 | private PrefManager getPrefManager() { 80 | if (sPrefManager == null) { 81 | synchronized (LoaderLS.class) { 82 | if (sPrefManager == null) { 83 | Logger.warning("creating new instance"); 84 | sPrefManager = new PrefManager(getApplicationContext()); 85 | } 86 | } 87 | } 88 | return sPrefManager; 89 | } 90 | 91 | private Helper getHelper() { 92 | if (sHelper == null) { 93 | synchronized (LoaderLS.class) { 94 | if (sHelper == null) { 95 | Logger.warning("creating new instance"); 96 | sHelper = new Helper(); 97 | } 98 | } 99 | } 100 | return sHelper; 101 | } 102 | 103 | private IDPub getIDPub() { 104 | if (sIDPub == null) { 105 | synchronized (LoaderLS.class) { 106 | if (sIDPub == null) { 107 | Logger.warning("creating new instance"); 108 | sIDPub = new IDPub(); 109 | } 110 | } 111 | } 112 | return sIDPub; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/PlayerWrapper.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import android.annotation.SuppressLint; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.content.res.Configuration; 8 | import android.graphics.Rect; 9 | import android.util.AttributeSet; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewConfiguration; 13 | import android.view.ViewGroup; 14 | import android.widget.RelativeLayout; 15 | 16 | import tv.twitch.android.mod.swipper.Swipper; 17 | import tv.twitch.android.mod.utils.Logger; 18 | 19 | public class PlayerWrapper extends RelativeLayout { 20 | private static final int PADDING_DEFAULT_IGNORE = 25; 21 | private static int PADDING_DEVICE_IGNORE = 25; 22 | 23 | private ViewGroup mPlayerOverlayContainer; 24 | private ViewGroup mDebugPanelContainer; 25 | private ViewGroup mFloatingChatContainer; 26 | private final Swipper mSwipper; 27 | 28 | private int mTouchSlop; 29 | 30 | private int mStartPosY = -1; 31 | private int mStartPosX = -1; 32 | private boolean bInScrollArea = false; 33 | 34 | 35 | public PlayerWrapper(Context context) { 36 | super(context); 37 | mSwipper = new Swipper((Activity) context); 38 | } 39 | 40 | public PlayerWrapper(Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | mSwipper = new Swipper((Activity) context); 43 | } 44 | 45 | public PlayerWrapper(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | mSwipper = new Swipper((Activity) context); 48 | } 49 | 50 | private float getDensity() { 51 | return this.getResources().getDisplayMetrics().density; 52 | } 53 | 54 | @Override 55 | protected void onFinishInflate() { 56 | super.onFinishInflate(); 57 | 58 | PADDING_DEVICE_IGNORE = Math.round(PADDING_DEFAULT_IGNORE * getDensity()); 59 | mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop() * 2; 60 | 61 | mPlayerOverlayContainer = findViewById(IDPub.PLAYER_OVERLAY_ID); 62 | if (mPlayerOverlayContainer == null) { 63 | Logger.error("mPlayerOverlayContainer is null. Update ID?"); 64 | return; 65 | } 66 | 67 | mFloatingChatContainer = findViewById(IDPub.FLOATING_CHAT_CONTAINER_ID); 68 | if (mFloatingChatContainer == null) { 69 | Logger.error("mFloatingChatContainer is null. Update ID?"); 70 | return; 71 | } 72 | 73 | mDebugPanelContainer = findViewById(IDPub.DEBUG_PANEL_CONTAINER_ID); 74 | if (mDebugPanelContainer == null) { 75 | Logger.error("mDebugPanelContainer is null. Update ID?"); 76 | return; 77 | } 78 | 79 | initializeSwipper(); 80 | } 81 | 82 | private void initializeSwipper() { 83 | mSwipper.setOverlay(mPlayerOverlayContainer); 84 | 85 | if (LoaderLS.getPrefManagerInstance().isVolumeSwipeEnabled()) 86 | mSwipper.enableVolumeSwipe(); 87 | 88 | if (LoaderLS.getPrefManagerInstance().isBrightnessSwipeEnabled()) 89 | mSwipper.enableBrightnessSwipe(); 90 | } 91 | 92 | @Override 93 | public boolean onInterceptTouchEvent(MotionEvent event) { 94 | final int action = event.getAction(); 95 | 96 | if (!mSwipper.isEnabled()) 97 | return false; 98 | 99 | switch (action) { 100 | case MotionEvent.ACTION_UP: 101 | case MotionEvent.ACTION_CANCEL: 102 | mStartPosY = -1; 103 | mStartPosX = -1; 104 | 105 | mSwipper.onTouchEvent(event); 106 | 107 | return false; 108 | case MotionEvent.ACTION_DOWN: 109 | mStartPosY = Math.round(event.getY()); 110 | mStartPosX = Math.round(event.getX()); 111 | 112 | bInScrollArea = checkArea(event); 113 | 114 | mSwipper.onTouchEvent(event); 115 | 116 | return false; 117 | case MotionEvent.ACTION_MOVE: { 118 | if (!bInScrollArea) 119 | return false; 120 | 121 | if (event.getPointerCount() > 1) { 122 | Logger.debug("Ignore scrolling: multi touch, val="+event.getPointerCount()); 123 | bInScrollArea = false; 124 | return false; 125 | } 126 | 127 | int diff = getDistance(event); 128 | if (diff > mTouchSlop) { 129 | Logger.debug("SCROLLING"); 130 | return mSwipper.onTouchEvent(event); 131 | } 132 | break; 133 | } 134 | case MotionEvent.ACTION_POINTER_DOWN: 135 | bInScrollArea = false; 136 | return false; 137 | } 138 | 139 | return false; 140 | } 141 | 142 | private static boolean isVisible(View view) { 143 | if (view == null) 144 | return false; 145 | 146 | return view.getVisibility() == VISIBLE; 147 | } 148 | 149 | private static boolean isHit(ViewGroup view, int x, int y) { 150 | if (view == null) { 151 | Logger.debug("view is null"); 152 | return false; 153 | } 154 | Rect hitRect = new Rect(); 155 | view.getHitRect(hitRect); 156 | 157 | return hitRect.contains(x, y); 158 | } 159 | 160 | private static View getFirstChild(ViewGroup viewGroup) { 161 | if (viewGroup == null) 162 | return null; 163 | 164 | if (viewGroup.getChildCount() < 1) 165 | return null; 166 | 167 | return viewGroup.getChildAt(0); 168 | } 169 | 170 | private boolean checkCollisions() { 171 | if (!isHit(mPlayerOverlayContainer, mStartPosX, mStartPosY)) { 172 | Logger.debug("Ignore scrolling: Wrong area: x=" + mStartPosX + ", y="+ mStartPosY); 173 | return false; 174 | } 175 | 176 | if (isVisible(mDebugPanelContainer)) { 177 | ViewGroup list = mDebugPanelContainer.findViewById(IDPub.VIDEO_DEBUG_LIST_ID); 178 | if (isVisible(getFirstChild(mDebugPanelContainer)) && isVisible(list) && isHit(list, mStartPosX, mStartPosY)) { 179 | Logger.debug("Ignore scrolling: Debug panel area: x=" + mStartPosX + ", y=" + mStartPosY); 180 | return false; 181 | } 182 | } 183 | 184 | if (isVisible(mFloatingChatContainer)) { 185 | ViewGroup container = mFloatingChatContainer.findViewById(IDPub.MESSAGES_CONTAINER_ID); 186 | if (isVisible(container) && isHit(container, mStartPosX, mStartPosY)) { 187 | Logger.debug("Ignore scrolling: Floating chat area: x=" + mStartPosX + ", y=" + mStartPosY); 188 | return false; 189 | } 190 | } 191 | 192 | return true; 193 | } 194 | 195 | private boolean checkArea(MotionEvent event) { 196 | if (mStartPosY <= PADDING_DEVICE_IGNORE) { 197 | Logger.debug("Ignore scrolling: top PADDING_IGNORE=" + PADDING_DEVICE_IGNORE +", val="+ mStartPosY); 198 | return false; 199 | } 200 | 201 | float overlayBottomY = mPlayerOverlayContainer.getY()+mPlayerOverlayContainer.getHeight(); 202 | if (mStartPosY >= (overlayBottomY - PADDING_DEVICE_IGNORE)) { 203 | Logger.debug("Ignore scrolling: bottom PADDING_IGNORE=" + PADDING_DEVICE_IGNORE +", val="+ overlayBottomY); 204 | return false; 205 | } 206 | 207 | if (event.getPointerCount() > 1) { 208 | Logger.debug("Ignore scrolling: multi touch, val="+event.getPointerCount()); 209 | return false; 210 | } 211 | 212 | if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { 213 | Logger.debug("Ignore scrolling: wrong orientation"); 214 | return false; 215 | } 216 | 217 | return checkCollisions(); 218 | } 219 | 220 | private int getDistance(MotionEvent moveEvent) { 221 | if (moveEvent == null) { 222 | Logger.debug("moveEvent is null"); 223 | return 0; 224 | } 225 | 226 | if (mStartPosY == -1) { 227 | Logger.debug("startPosY == -1"); 228 | return 0; 229 | } 230 | 231 | return Math.abs(mStartPosY - Math.round(moveEvent.getY())); 232 | } 233 | 234 | @SuppressLint("ClickableViewAccessibility") 235 | @Override 236 | public boolean onTouchEvent(MotionEvent event) { 237 | return mSwipper.onTouchEvent(event); 238 | } 239 | } -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/bridges/RetrofitUtils.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.bridges; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | import retrofit2.Retrofit; 7 | import retrofit2.converter.gson.GsonConverterFactory; 8 | 9 | 10 | public class RetrofitUtils { 11 | public static Retrofit getRetrofitClient(String baseUrl) { 12 | return new retrofit2.Retrofit.Builder() 13 | .baseUrl(baseUrl) 14 | .addConverterFactory(GsonConverterFactory.create(new Gson())) 15 | .build(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/BaseChannelSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | public abstract class BaseChannelSet extends BaseEmoteSet { 5 | private final int mChannelId; 6 | 7 | public BaseChannelSet(int channelId) { 8 | mChannelId = channelId; 9 | } 10 | 11 | public int getChannelId() { 12 | return mChannelId; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/BaseEmoteSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import android.text.TextUtils; 5 | 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | 11 | import tv.twitch.android.mod.bridges.ApiCallback; 12 | import tv.twitch.android.mod.models.Emote; 13 | import tv.twitch.android.mod.models.EmoteSet; 14 | 15 | 16 | public abstract class BaseEmoteSet extends ApiCallback implements EmoteSet { 17 | private final Map mEmoteMap = Collections.synchronizedMap(new LinkedHashMap()); 18 | 19 | 20 | @Override 21 | public void addEmote(Emote emote) { 22 | if (emote == null) 23 | return; 24 | 25 | if (TextUtils.isEmpty(emote.getCode())) 26 | return; 27 | 28 | mEmoteMap.put(emote.getCode(), emote); 29 | } 30 | 31 | @Override 32 | public Emote getEmote(String name) { 33 | return mEmoteMap.get(name); 34 | } 35 | 36 | @Override 37 | public Collection getEmotes() { 38 | return mEmoteMap.values(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/BttvChannelSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import android.text.TextUtils; 5 | 6 | import java.util.List; 7 | 8 | import retrofit2.Call; 9 | import tv.twitch.android.mod.models.BttvEmoteModel; 10 | import tv.twitch.android.mod.models.api.BttvChannelResponse; 11 | import tv.twitch.android.mod.models.api.BttvEmoteResponse; 12 | import tv.twitch.android.mod.models.api.FailReason; 13 | import tv.twitch.android.mod.utils.Logger; 14 | 15 | import static tv.twitch.android.mod.net.ServiceFactory.getBttvApi; 16 | 17 | 18 | public class BttvChannelSet extends BaseChannelSet { 19 | 20 | BttvChannelSet(int channelId) { 21 | super(channelId); 22 | } 23 | 24 | @Override 25 | public void fetch() { 26 | getBttvApi().getBttvEmotes(getChannelId()).enqueue(this); 27 | } 28 | 29 | @Override 30 | public void onRequestSuccess(BttvChannelResponse bttvResponse) { 31 | List channelEmotes = bttvResponse.getChannelEmotes(); 32 | if (channelEmotes != null) { 33 | for (BttvEmoteResponse emoticon : channelEmotes) { 34 | if (emoticon == null) 35 | continue; 36 | 37 | if (TextUtils.isEmpty(emoticon.getId())) { 38 | continue; 39 | } 40 | 41 | if (TextUtils.isEmpty(emoticon.getCode())) { 42 | continue; 43 | } 44 | 45 | if (emoticon.getImageType() == null) { 46 | continue; 47 | } 48 | 49 | addEmote(new BttvEmoteModel(emoticon.getCode(), emoticon.getId(), emoticon.getImageType())); 50 | } 51 | } 52 | 53 | List sharedEmotes = bttvResponse.getSharedEmotes(); 54 | if (sharedEmotes != null) { 55 | for (BttvEmoteResponse emoticon : sharedEmotes) { 56 | if (emoticon == null) 57 | continue; 58 | 59 | if (TextUtils.isEmpty(emoticon.getId())) { 60 | continue; 61 | } 62 | 63 | if (TextUtils.isEmpty(emoticon.getCode())) { 64 | continue; 65 | } 66 | 67 | if (emoticon.getImageType() == null) { 68 | continue; 69 | } 70 | 71 | addEmote(new BttvEmoteModel(emoticon.getCode(), emoticon.getId(), emoticon.getImageType())); 72 | } 73 | } 74 | } 75 | 76 | @Override 77 | public void onRequestFail(Call call, FailReason failReason) { 78 | Logger.debug("channelId=" + getChannelId() + ", reason="+failReason.toString()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/BttvGlobalSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import android.text.TextUtils; 5 | 6 | import java.util.List; 7 | 8 | import retrofit2.Call; 9 | import tv.twitch.android.mod.models.BttvEmoteModel; 10 | import tv.twitch.android.mod.models.api.BttvEmoteResponse; 11 | import tv.twitch.android.mod.models.api.FailReason; 12 | import tv.twitch.android.mod.utils.Logger; 13 | 14 | import static tv.twitch.android.mod.net.ServiceFactory.getBttvApi; 15 | 16 | 17 | public class BttvGlobalSet extends BaseEmoteSet> { 18 | 19 | @Override 20 | public synchronized void fetch() { 21 | getBttvApi().getGlobalEmotes().enqueue(this); 22 | } 23 | 24 | @Override 25 | public void onRequestSuccess(List bttvResponse) { 26 | for (BttvEmoteResponse emoticon : bttvResponse) { 27 | if (emoticon == null) 28 | continue; 29 | 30 | if (TextUtils.isEmpty(emoticon.getId())) { 31 | continue; 32 | } 33 | 34 | if (TextUtils.isEmpty(emoticon.getCode())) { 35 | continue; 36 | } 37 | 38 | if (emoticon.getImageType() == null) { 39 | Logger.debug("getImageType() is null"); 40 | continue; 41 | } 42 | 43 | addEmote(new BttvEmoteModel(emoticon.getCode(), emoticon.getId(), emoticon.getImageType())); 44 | } 45 | } 46 | 47 | @Override 48 | public void onRequestFail(Call> call, FailReason failReason) { 49 | Logger.debug("reason="+failReason.toString()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/EmoteManager.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import android.text.TextUtils; 5 | import android.util.LruCache; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | 10 | import tv.twitch.android.mod.models.Emote; 11 | import tv.twitch.android.mod.utils.Logger; 12 | 13 | 14 | public class EmoteManager { 15 | private static final int MAX_CACHE_SIZE = 20; 16 | private static final BttvGlobalSet sGlobalEmotes = new BttvGlobalSet(); 17 | 18 | private final RoomCache mRoomCache = new RoomCache(MAX_CACHE_SIZE); 19 | 20 | public EmoteManager() { 21 | fetchGlobalEmotes(); 22 | } 23 | 24 | private static class RoomCache extends LruCache { 25 | public RoomCache(int maxSize) { 26 | super(maxSize); 27 | } 28 | 29 | @Override 30 | protected Room create(Integer key) { 31 | Room room = new Room(key); 32 | room.requestEmotes(); 33 | 34 | return room; 35 | } 36 | } 37 | 38 | private void fetchGlobalEmotes() { 39 | Logger.info("Fetching global emoticons..."); 40 | sGlobalEmotes.fetch(); 41 | } 42 | 43 | public void requestEmotes(int channelId, boolean force) { 44 | if (channelId == 0) { 45 | Logger.warning("Cannot request emotes: channelId==0"); 46 | return; 47 | } 48 | 49 | Logger.debug("New emote request: " + channelId); 50 | if (force) { 51 | synchronized (mRoomCache) { 52 | mRoomCache.put(channelId, mRoomCache.create(channelId)); 53 | } 54 | } else { 55 | mRoomCache.get(channelId); 56 | } 57 | } 58 | 59 | public Collection getGlobalEmotes() { 60 | return sGlobalEmotes.getEmotes(); 61 | } 62 | 63 | public Collection getBttvEmotes(int channelId) { 64 | if (channelId == 0) { 65 | return new ArrayList<>(); 66 | } 67 | 68 | return mRoomCache.get(channelId).getBttvEmotes(); 69 | } 70 | 71 | public Collection getFfzEmotes(int channelId) { 72 | if (channelId == 0) { 73 | return new ArrayList<>(); 74 | } 75 | 76 | return mRoomCache.get(channelId).getFfzEmotes(); 77 | } 78 | 79 | public Emote getEmote(String code, int channelId) { 80 | if (TextUtils.isEmpty(code)) 81 | return null; 82 | 83 | if (channelId != 0) { 84 | Emote emote = mRoomCache.get(channelId).findEmote(code); 85 | if (emote != null) 86 | return emote; 87 | } 88 | 89 | return sGlobalEmotes.getEmote(code); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/FfzChannelSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import android.text.TextUtils; 5 | 6 | import java.util.List; 7 | 8 | import retrofit2.Call; 9 | import tv.twitch.android.mod.models.FfzEmoteModel; 10 | import tv.twitch.android.mod.models.api.FailReason; 11 | import tv.twitch.android.mod.models.api.FfzEmoteResponse; 12 | import tv.twitch.android.mod.utils.Logger; 13 | 14 | import static tv.twitch.android.mod.net.ServiceFactory.getBttvApi; 15 | 16 | 17 | public class FfzChannelSet extends BaseChannelSet> { 18 | 19 | public FfzChannelSet(int channelId) { 20 | super(channelId); 21 | } 22 | 23 | @Override 24 | public void fetch() { 25 | getBttvApi().getFfzEmotes(getChannelId()).enqueue(this); 26 | } 27 | 28 | @Override 29 | public void onRequestSuccess(List ffzResponse) { 30 | for (FfzEmoteResponse emoteResponse : ffzResponse) { 31 | if (emoteResponse == null) 32 | continue; 33 | 34 | if (emoteResponse.getImages() == null) 35 | continue; 36 | 37 | if (emoteResponse.getImages().isEmpty()) 38 | continue; 39 | 40 | if (TextUtils.isEmpty(emoteResponse.getId())) 41 | continue; 42 | 43 | FfzEmoteModel emote = new FfzEmoteModel(emoteResponse.getCode(), emoteResponse.getId(), emoteResponse.getImages()); 44 | addEmote(emote); 45 | } 46 | } 47 | 48 | 49 | @Override 50 | public void onRequestFail(Call> call, FailReason failReason) { 51 | Logger.debug("channelId=" + getChannelId() + ", reason="+failReason.toString()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/emotes/Room.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.emotes; 2 | 3 | 4 | import java.util.Collection; 5 | 6 | import tv.twitch.android.mod.models.Emote; 7 | import tv.twitch.android.mod.utils.Logger; 8 | 9 | 10 | class Room { 11 | private final int mChannelId; 12 | 13 | private final BttvChannelSet mBttvSet; 14 | private final FfzChannelSet mFfzSet; 15 | 16 | 17 | public Room(int channelId) { 18 | Logger.debug("New room: " + channelId); 19 | mChannelId = channelId; 20 | mBttvSet = new BttvChannelSet(getChannelId()); 21 | mFfzSet = new FfzChannelSet(getChannelId()); 22 | requestEmotes(); 23 | } 24 | 25 | public synchronized void requestEmotes() { 26 | mBttvSet.fetch(); 27 | mFfzSet.fetch(); 28 | } 29 | 30 | public final Emote findEmote(String emoteName) { 31 | Emote emote = mBttvSet.getEmote(emoteName); 32 | if (emote != null) 33 | return emote; 34 | 35 | return mFfzSet.getEmote(emoteName); 36 | } 37 | 38 | public final Collection getBttvEmotes() { 39 | return mBttvSet.getEmotes(); 40 | } 41 | 42 | public final Collection getFfzEmotes() { 43 | return mFfzSet.getEmotes(); 44 | } 45 | 46 | public int getChannelId() { 47 | return mChannelId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/BttvEmoteModel.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models; 2 | 3 | 4 | import tv.twitch.android.mod.bridges.ChatFactory; 5 | import tv.twitch.android.mod.models.api.ImageType; 6 | import tv.twitch.android.mod.models.settings.EmoteSize; 7 | import tv.twitch.chat.ChatEmoticon; 8 | 9 | 10 | public final class BttvEmoteModel implements Emote { 11 | private static final String sUrlTemplate = "https://cdn.betterttv.net/emote/{id}/{size}"; 12 | 13 | private final String mCode; 14 | private final String mId; 15 | private final boolean isGif; 16 | 17 | private String url1x = null; 18 | private String url2x = null; 19 | private String url3x = null; 20 | 21 | private ChatEmoticon ce = null; 22 | 23 | public BttvEmoteModel(String code, String id, ImageType imageType) { 24 | this.mCode = code; 25 | this.mId = id; 26 | this.isGif = imageType == ImageType.GIF; 27 | } 28 | 29 | @Override 30 | public String getCode() { 31 | return mCode; 32 | } 33 | 34 | @Override 35 | public String getUrl(EmoteSize size) { 36 | switch (size) { 37 | case LARGE: 38 | return getUrl3x(); 39 | default: 40 | case MEDIUM: 41 | return getUrl2x(); 42 | case SMALL: 43 | return getUrl1x(); 44 | } 45 | } 46 | 47 | @Override 48 | public String getId() { 49 | return mId; 50 | } 51 | 52 | @Override 53 | public boolean isGif() { 54 | return isGif; 55 | } 56 | 57 | @Override 58 | public ChatEmoticon getChatEmoticon() { 59 | if (ce == null) { 60 | synchronized (this) { 61 | if (ce == null) { 62 | this.ce = ChatFactory.getEmoticon(getUrl(EmoteSize.LARGE), getCode()); 63 | } 64 | } 65 | } 66 | 67 | return ce; 68 | } 69 | 70 | private String getUrl1x() { 71 | if (url1x == null) { 72 | synchronized (this) { 73 | if (url1x == null) { 74 | url1x = sUrlTemplate.replace("{id}", getId()).replace("{size}", "1x"); 75 | } 76 | } 77 | } 78 | 79 | return url1x; 80 | } 81 | 82 | private String getUrl2x() { 83 | if (url2x == null) { 84 | synchronized (this) { 85 | if (url2x == null) { 86 | url2x = sUrlTemplate.replace("{id}", getId()).replace("{size}", "2x"); 87 | } 88 | } 89 | } 90 | 91 | return url2x; 92 | } 93 | 94 | private String getUrl3x() { 95 | if (url3x == null) { 96 | synchronized (this) { 97 | if (url3x == null) { 98 | url3x = sUrlTemplate.replace("{id}", getId()).replace("{size}", "3x"); 99 | } 100 | } 101 | } 102 | 103 | return url3x; 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | return "{Code: " + getCode() + ", Id: " + getId() + ", isGif: " + isGif() + "}"; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/Emote.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models; 2 | 3 | 4 | import tv.twitch.android.mod.models.settings.EmoteSize; 5 | import tv.twitch.chat.ChatEmoticon; 6 | 7 | 8 | public interface Emote { 9 | String getCode(); 10 | 11 | String getUrl(EmoteSize size); 12 | 13 | String getId(); 14 | 15 | boolean isGif(); 16 | 17 | ChatEmoticon getChatEmoticon(); 18 | } 19 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/EmoteSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models; 2 | 3 | 4 | import java.util.Collection; 5 | 6 | 7 | public interface EmoteSet { 8 | void addEmote(Emote emote); 9 | 10 | Emote getEmote(String name); 11 | 12 | Collection getEmotes(); 13 | } 14 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/FfzEmoteModel.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models; 2 | 3 | 4 | import android.text.TextUtils; 5 | 6 | import java.util.HashMap; 7 | 8 | import tv.twitch.android.mod.bridges.ChatFactory; 9 | import tv.twitch.android.mod.models.settings.EmoteSize; 10 | import tv.twitch.chat.ChatEmoticon; 11 | 12 | 13 | public class FfzEmoteModel implements Emote { 14 | private final String mCode; 15 | private final String mId; 16 | 17 | private String url1x = null; 18 | private String url2x = null; 19 | private String url3x = null; 20 | 21 | private ChatEmoticon ce = null; 22 | 23 | public FfzEmoteModel(String code, String id, HashMap urls) { 24 | this.mCode = code; 25 | this.mId = id; 26 | 27 | for (String key : urls.keySet()) { 28 | String url = urls.get(key); 29 | if (TextUtils.isEmpty(url)) 30 | continue; 31 | 32 | if (url.startsWith("//")) 33 | url = "https:" + url; 34 | switch (key) { 35 | case "1x": 36 | url1x = url; 37 | break; 38 | case "2x": 39 | url2x = url; 40 | break; 41 | case "4x": 42 | url3x = url; 43 | break; 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public String getCode() { 50 | return mCode; 51 | } 52 | 53 | @Override 54 | public String getUrl(EmoteSize size) { 55 | switch (size) { 56 | case LARGE: 57 | if (url3x != null) 58 | return url3x; 59 | default: 60 | case MEDIUM: 61 | if (url2x != null) 62 | return url2x; 63 | case SMALL: 64 | if (url1x != null) 65 | return url1x; 66 | } 67 | 68 | return ""; 69 | } 70 | 71 | @Override 72 | public String getId() { 73 | return mId; 74 | } 75 | 76 | @Override 77 | public boolean isGif() { 78 | return false; 79 | } 80 | 81 | @Override 82 | public ChatEmoticon getChatEmoticon() { 83 | if (ce == null) { 84 | synchronized (this) { 85 | if (ce == null) { 86 | this.ce = ChatFactory.getEmoticon(getUrl(EmoteSize.LARGE), getCode()); 87 | } 88 | } 89 | } 90 | 91 | return ce; 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return "{Code: " + getCode() + ", Id: " + getId() + ", isGif: " + isGif() + "}"; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/PreferenceItem.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models; 2 | 3 | public interface PreferenceItem { 4 | String getPreferenceValue(); 5 | 6 | String getPreferenceKey(); 7 | 8 | String getPreferenceName(); 9 | 10 | PreferenceItem getPreference(String name); 11 | } 12 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/api/BttvChannelResponse.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.api; 2 | 3 | 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.List; 7 | 8 | 9 | public class BttvChannelResponse { 10 | @SerializedName("channelEmotes") 11 | private List channelEmotes; 12 | @SerializedName("sharedEmotes") 13 | private List sharedEmotes; 14 | 15 | public List getChannelEmotes() { 16 | return channelEmotes; 17 | } 18 | 19 | public List getSharedEmotes() { 20 | return sharedEmotes; 21 | } 22 | 23 | public void setChannelEmotes(List channelEmotes) { 24 | this.channelEmotes = channelEmotes; 25 | } 26 | 27 | public void setSharedEmotes(List sharedEmotes) { 28 | this.sharedEmotes = sharedEmotes; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/api/BttvEmoteResponse.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.api; 2 | 3 | 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | 7 | public class BttvEmoteResponse { 8 | @SerializedName("id") 9 | private String emoteId; 10 | @SerializedName("code") 11 | private String code; 12 | @SerializedName("imageType") 13 | private ImageType imageType; 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getId() { 20 | return emoteId; 21 | } 22 | 23 | public ImageType getImageType() { 24 | return imageType; 25 | } 26 | 27 | public void setImageType(ImageType imageType) { 28 | this.imageType = imageType; 29 | } 30 | 31 | public void setId(String emoteId) { 32 | this.emoteId = emoteId; 33 | } 34 | 35 | public void setCode(String code) { 36 | this.code = code; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/api/FailReason.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.api; 2 | 3 | public enum FailReason { 4 | UNKNOWN, 5 | NOT_FOUND, 6 | UNSUCCESSFUL, 7 | NULL_BODY, 8 | EXCEPTION 9 | } 10 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/api/FfzEmoteResponse.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.api; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.HashMap; 6 | 7 | public class FfzEmoteResponse { 8 | @SerializedName("id") 9 | private String emoteId; 10 | @SerializedName("code") 11 | private String code; 12 | @SerializedName("images") 13 | private HashMap images; 14 | 15 | public String getId() { 16 | return emoteId; 17 | } 18 | 19 | public String getCode() { 20 | return code; 21 | } 22 | 23 | public HashMap getImages() { 24 | return images; 25 | } 26 | 27 | public void setId(String emoteId) { 28 | this.emoteId = emoteId; 29 | } 30 | 31 | public void setCode(String code) { 32 | this.code = code; 33 | } 34 | 35 | public void setImages(HashMap images) { 36 | this.images = images; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/api/ImageType.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.api; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public enum ImageType { 6 | @SerializedName("png") 7 | PNG("png"), 8 | @SerializedName("gif") 9 | GIF("gif"); 10 | 11 | private final String mValue; 12 | 13 | ImageType(String value) { 14 | mValue = value; 15 | } 16 | 17 | public String getValue() { 18 | return mValue; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/EmotePickerView.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum EmotePickerView implements PreferenceItem { 7 | AUTO("Auto", "AUTO"), 8 | OLD("Old", "OLD"), 9 | NEW("New", "NEW"); 10 | 11 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_EMOTE_PICKER_VIEW; 12 | 13 | public final String name; 14 | public final String value; 15 | 16 | 17 | EmotePickerView(String name, String preferenceKey) { 18 | this.name = name; 19 | this.value = preferenceKey; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return getPreferenceName(); 25 | } 26 | 27 | @Override 28 | public String getPreferenceValue() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public String getPreferenceKey() { 34 | return PREFERENCE_KEY; 35 | } 36 | 37 | @Override 38 | public String getPreferenceName() { 39 | return name; 40 | } 41 | 42 | @Override 43 | public PreferenceItem getPreference(String value) { 44 | for (PreferenceItem item : values()) { 45 | if (item.getPreferenceValue().equals(value)) 46 | return item; 47 | } 48 | 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/EmoteSize.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | 7 | public enum EmoteSize implements PreferenceItem { 8 | SMALL("Small", "SMALL"), 9 | MEDIUM("Medium", "MEDIUM"), 10 | LARGE("Large", "LARGE"); 11 | 12 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_EMOTE_SIZE; 13 | 14 | public final String name; 15 | public final String value; 16 | 17 | 18 | EmoteSize(String name, String preferenceKey) { 19 | this.name = name; 20 | this.value = preferenceKey; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return getPreferenceName(); 26 | } 27 | 28 | @Override 29 | public String getPreferenceValue() { 30 | return value; 31 | } 32 | 33 | @Override 34 | public String getPreferenceKey() { 35 | return PREFERENCE_KEY; 36 | } 37 | 38 | @Override 39 | public String getPreferenceName() { 40 | return name; 41 | } 42 | 43 | @Override 44 | public PreferenceItem getPreference(String value) { 45 | for (PreferenceItem item : values()) { 46 | if (item.getPreferenceValue().equals(value)) 47 | return item; 48 | } 49 | 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/ExoPlayerSpeed.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum ExoPlayerSpeed implements PreferenceItem { 7 | DEFAULT("Default", "1.0"), 8 | SPEED1("1.25", "1.25"), 9 | SPEED2("1.5", "1.5"), 10 | SPEED3("1.75", "1.75"), 11 | SPEED4("2.0", "2.0"); 12 | 13 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_EXOPLAYER_SPEED; 14 | 15 | public final String name; 16 | public final String value; 17 | 18 | 19 | ExoPlayerSpeed(String name, String preferenceKey) { 20 | this.name = name; 21 | this.value = preferenceKey; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return getPreferenceName(); 27 | } 28 | 29 | @Override 30 | public String getPreferenceValue() { 31 | return value; 32 | } 33 | 34 | @Override 35 | public String getPreferenceKey() { 36 | return PREFERENCE_KEY; 37 | } 38 | 39 | @Override 40 | public String getPreferenceName() { 41 | return name; 42 | } 43 | 44 | @Override 45 | public PreferenceItem getPreference(String value) { 46 | for (PreferenceItem item : values()) { 47 | if (item.getPreferenceValue().equals(value)) 48 | return item; 49 | } 50 | 51 | return this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/FollowView.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum FollowView implements PreferenceItem { 7 | AUTO("Auto", "AUTO"), 8 | OLD("Old", "OLD"), 9 | NEW("New", "NEW"); 10 | 11 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_FOLLOW_VIEW; 12 | 13 | public final String name; 14 | public final String value; 15 | 16 | 17 | FollowView(String name, String preferenceKey) { 18 | this.name = name; 19 | this.value = preferenceKey; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return getPreferenceName(); 25 | } 26 | 27 | @Override 28 | public String getPreferenceValue() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public String getPreferenceKey() { 34 | return PREFERENCE_KEY; 35 | } 36 | 37 | @Override 38 | public String getPreferenceName() { 39 | return name; 40 | } 41 | 42 | @Override 43 | public PreferenceItem getPreference(String value) { 44 | for (PreferenceItem item : values()) { 45 | if (item.getPreferenceValue().equals(value)) 46 | return item; 47 | } 48 | 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/Gifs.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum Gifs implements PreferenceItem { 7 | DISABLED("Disabled", "DISABLED"), 8 | STATIC("Enabled (Static)", "STATIC"), 9 | ANIMATED("Enabled (Animated)", "ANIMATED"); 10 | 11 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_GIFS; 12 | 13 | public final String name; 14 | public final String value; 15 | 16 | 17 | Gifs(String name, String preferenceKey) { 18 | this.name = name; 19 | this.value = preferenceKey; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return getPreferenceName(); 25 | } 26 | 27 | @Override 28 | public String getPreferenceValue() { 29 | return value; 30 | } 31 | 32 | @Override 33 | public String getPreferenceKey() { 34 | return PREFERENCE_KEY; 35 | } 36 | 37 | @Override 38 | public String getPreferenceName() { 39 | return name; 40 | } 41 | 42 | @Override 43 | public PreferenceItem getPreference(String value) { 44 | for (PreferenceItem item : values()) { 45 | if (item.getPreferenceValue().equals(value)) 46 | return item; 47 | } 48 | 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/MiniPlayerSize.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum MiniPlayerSize implements PreferenceItem { 7 | DEFAULT("Default", "1.0"), 8 | SIZE1("1.25", "1.25"), 9 | SIZE2("1.35", "1.35"), 10 | SIZE3("1.5", "1.5"), 11 | SIZE4("1.65", "1.65"), 12 | SIZE5("1.75", "1.75"), 13 | SIZE6("1.85", "1.85"), 14 | SIZE7("2.0", "2.0"); 15 | 16 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_MINIPLAYER_SIZE; 17 | 18 | public final String name; 19 | public final String value; 20 | 21 | 22 | MiniPlayerSize(String name, String preferenceKey) { 23 | this.name = name; 24 | this.value = preferenceKey; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return getPreferenceName(); 30 | } 31 | 32 | @Override 33 | public String getPreferenceValue() { 34 | return value; 35 | } 36 | 37 | @Override 38 | public String getPreferenceKey() { 39 | return PREFERENCE_KEY; 40 | } 41 | 42 | @Override 43 | public String getPreferenceName() { 44 | return name; 45 | } 46 | 47 | @Override 48 | public PreferenceItem getPreference(String value) { 49 | for (PreferenceItem item : values()) { 50 | if (item.getPreferenceValue().equals(value)) 51 | return item; 52 | } 53 | 54 | return this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/PlayerImpl.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | 7 | public enum PlayerImpl implements PreferenceItem { 8 | AUTO("Auto", "AUTO"), 9 | CORE("Twitch player", "playercore"), 10 | EXO("ExoPlayer V2", "exoplayer_2"); 11 | 12 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_PLAYER; 13 | 14 | public final String name; 15 | public final String value; 16 | 17 | 18 | PlayerImpl(String name, String preferenceKey) { 19 | this.name = name; 20 | this.value = preferenceKey; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return getPreferenceName(); 26 | } 27 | 28 | @Override 29 | public String getPreferenceValue() { 30 | return value; 31 | } 32 | 33 | @Override 34 | public String getPreferenceKey() { 35 | return PREFERENCE_KEY; 36 | } 37 | 38 | @Override 39 | public String getPreferenceName() { 40 | return name; 41 | } 42 | 43 | @Override 44 | public PreferenceItem getPreference(String value) { 45 | for (PreferenceItem item : values()) { 46 | if (item.getPreferenceValue().equals(value)) 47 | return item; 48 | } 49 | 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/models/settings/UserMessagesFiltering.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.models.settings; 2 | 3 | import tv.twitch.android.mod.models.PreferenceItem; 4 | import tv.twitch.android.mod.settings.PrefManager; 5 | 6 | public enum UserMessagesFiltering implements PreferenceItem { 7 | DISABLED("Disabled", "DISABLED"), 8 | PLEBS("Plebs", "PLEBS"), 9 | SUBS("Subs", "SUBS"), 10 | MODS("Mods", "MODS"), 11 | BROADCASTER("Broadcaster", "BROADCASTER"); 12 | 13 | private static final String PREFERENCE_KEY = PrefManager.PREF_KEY_USER_MESSAGES_FILTERING; 14 | 15 | public final String name; 16 | public final String value; 17 | 18 | 19 | UserMessagesFiltering(String name, String preferenceKey) { 20 | this.name = name; 21 | this.value = preferenceKey; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return getPreferenceName(); 27 | } 28 | 29 | @Override 30 | public String getPreferenceValue() { 31 | return value; 32 | } 33 | 34 | @Override 35 | public String getPreferenceKey() { 36 | return PREFERENCE_KEY; 37 | } 38 | 39 | @Override 40 | public String getPreferenceName() { 41 | return name; 42 | } 43 | 44 | @Override 45 | public PreferenceItem getPreference(String value) { 46 | for (PreferenceItem item : values()) { 47 | if (item.getPreferenceValue().equals(value)) 48 | return item; 49 | } 50 | 51 | return this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/net/BttvApi.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.net; 2 | 3 | 4 | import java.util.List; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Path; 9 | import tv.twitch.android.mod.models.api.BttvChannelResponse; 10 | import tv.twitch.android.mod.models.api.BttvEmoteResponse; 11 | import tv.twitch.android.mod.models.api.FfzEmoteResponse; 12 | 13 | 14 | public interface BttvApi { 15 | @GET("/3/cached/emotes/global") 16 | Call> getGlobalEmotes(); 17 | 18 | @GET("/3/cached/users/twitch/{id}") 19 | Call getBttvEmotes(@Path("id") int channelId); 20 | 21 | @GET("/3/cached/frankerfacez/users/twitch/{id}") 22 | Call> getFfzEmotes(@Path("id") int channelId); 23 | } 24 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/net/ServiceFactory.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.net; 2 | 3 | 4 | import static tv.twitch.android.mod.bridges.RetrofitUtils.getRetrofitClient; 5 | 6 | 7 | public class ServiceFactory { 8 | private static final String BTTV_API = "https://api.betterttv.net/"; 9 | 10 | private static volatile BttvApi mBttvApi; 11 | 12 | 13 | public static BttvApi getBttvApi() { 14 | if (mBttvApi == null) { 15 | synchronized (ServiceFactory.class) { 16 | if (mBttvApi == null) 17 | mBttvApi = getRetrofitClient(BTTV_API).create(BttvApi.class); 18 | } 19 | } 20 | 21 | return mBttvApi; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/settings/PrefManager.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.settings; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.PreferenceManager; 7 | import android.text.TextUtils; 8 | 9 | import tv.twitch.android.mod.models.settings.EmotePickerView; 10 | import tv.twitch.android.mod.models.settings.FollowView; 11 | import tv.twitch.android.mod.models.settings.UserMessagesFiltering; 12 | import tv.twitch.android.mod.models.settings.EmoteSize; 13 | import tv.twitch.android.mod.models.settings.ExoPlayerSpeed; 14 | import tv.twitch.android.mod.models.settings.Gifs; 15 | import tv.twitch.android.mod.models.settings.MiniPlayerSize; 16 | import tv.twitch.android.mod.models.settings.PlayerImpl; 17 | import tv.twitch.android.mod.models.PreferenceItem; 18 | import tv.twitch.android.mod.utils.Logger; 19 | 20 | 21 | public class PrefManager implements SharedPreferences.OnSharedPreferenceChangeListener { 22 | public static final String PREF_KEY_EMOTES = "MOD_EMOTES"; 23 | public static final String PREF_KEY_EMOTE_PICKER = "MOD_EMOTE_PICKER"; 24 | public static final String PREF_KEY_TIMESTAMPS = "MOD_TIMESTAMPS"; 25 | public static final String PREF_KEY_PLAYER = "MOD_PLAYER"; 26 | public static final String PREF_KEY_COPY_MSG = "MOD_COPY_MSG"; 27 | public static final String PREF_KEY_DISABLE_PLAYER_AUTOPLAY = "MOD_DISABLE_AUTOPLAY"; 28 | public static final String PREF_KEY_DISABLE_RECENT_SEARCH = "MOD_DISABLE_RECENT_SEARCH"; 29 | public static final String PREF_KEY_EXOPLAYER_SPEED = "MOD_EXOPLAYER_SPEED_NEW"; 30 | public static final String PREF_KEY_MINIPLAYER_SIZE = "MOD_MINIPLAYER_SIZE_NEW"; 31 | public static final String PREF_KEY_EMOTE_SIZE = "MOD_EMOTE_SIZE_NEW"; 32 | public static final String PREF_KEY_VIDEO_DEBUG = "MOD_VIDEO_DEBUG"; 33 | public static final String PREF_KEY_SWIPE_VOLUME = "MOD_SWIPE_VOLUME"; 34 | public static final String PREF_KEY_SWIPE_BRIGHTNESS = "MOD_SWIPE_BRIGHTNESS"; 35 | public static final String PREF_KEY_DEV_MODE = "MOD_DEV_MOD"; 36 | public static final String PREF_KEY_INTERCEPTOR = "MOD_INTERCEPTOR"; 37 | public static final String PREF_KEY_HIDE_DISCOVER = "MOD_HIDE_NAVIGATION_DISCOVER"; 38 | public static final String PREF_KEY_HIDE_ESPORTS = "MOD_HIDE_NAVIGATION_ESPORTS"; 39 | public static final String PREF_KEY_FLOATING_CHAT = "MOD_FLOATING_CHAT"; 40 | public static final String PREF_KEY_GIFS = "MOD_GIFS_STRATEGY"; 41 | public static final String PREF_KEY_USER_MESSAGES_FILTERING = "MOD_USER_MESSAGES_FILTERING"; 42 | public static final String PREF_KEY_EMOTE_PICKER_VIEW = "MOD_EMOTE_PICKER_VIEW"; 43 | public static final String PREF_KEY_FOLLOW_VIEW = "MOD_FOLLOW_VIEW"; 44 | 45 | public static final String PREF_KEY_TWITCH_DARK_THEME = "dark_theme_enabled"; 46 | 47 | public static final String PREF_KEY_DISABLE_RECOMMENDATIONS = "MOD_DISABLE_RECOMMENDATIONS"; 48 | public static final String PREF_KEY_DISABLE_FOLLOWED_GAMES = "MOD_DISABLE_FOLLOWED_GAMES"; 49 | public static final String PREF_KEY_DISABLE_RECENT_WATCHING = "MOD_DISABLE_RESUME_WATCHING"; 50 | 51 | private final SharedPreferences mPref; 52 | 53 | private EmoteSize mEmoteSize; 54 | private UserMessagesFiltering mUserMessagesFiltering; 55 | private Gifs mGifsStrategy; 56 | private boolean isDarkThemeEnabled; 57 | 58 | public PrefManager(Context context) { 59 | mPref = PreferenceManager.getDefaultSharedPreferences(context); 60 | mPref.registerOnSharedPreferenceChangeListener(this); 61 | 62 | initLocalPreferences(); 63 | } 64 | 65 | public boolean isCopyMsgOn() { 66 | return getBoolean(PREF_KEY_COPY_MSG, false); 67 | } 68 | 69 | public boolean isEmotesOn() { 70 | return getBoolean(PREF_KEY_EMOTES, true); 71 | } 72 | 73 | public boolean isHookEmoticonSetOn() { 74 | return getBoolean(PREF_KEY_EMOTE_PICKER, true); 75 | } 76 | 77 | public boolean isTimestampsOn() { 78 | return getBoolean(PREF_KEY_TIMESTAMPS, false); 79 | } 80 | 81 | public PlayerImpl getPlayer() { 82 | return (PlayerImpl) getOrDefault(PlayerImpl.AUTO); 83 | } 84 | 85 | public boolean isDisableAutoplay() { 86 | return getBoolean(PREF_KEY_DISABLE_PLAYER_AUTOPLAY, false); 87 | } 88 | 89 | public boolean isShowVideoDebugPanel() { 90 | return getBoolean(PREF_KEY_VIDEO_DEBUG, false); 91 | } 92 | 93 | public boolean isDisableRecentSearch() { 94 | return getBoolean(PREF_KEY_DISABLE_RECENT_SEARCH, false); 95 | } 96 | 97 | public boolean isDisableRecentWatching() { 98 | return getBoolean(PREF_KEY_DISABLE_RECENT_WATCHING, false); 99 | } 100 | 101 | public boolean isDisableRecommendations() { 102 | return getBoolean(PREF_KEY_DISABLE_RECOMMENDATIONS, false); 103 | } 104 | 105 | public boolean isDisableFollowedGames() { 106 | return getBoolean(PREF_KEY_DISABLE_FOLLOWED_GAMES, false); 107 | } 108 | 109 | public boolean isVolumeSwipeEnabled() { 110 | return getBoolean(PREF_KEY_SWIPE_VOLUME, false); 111 | } 112 | 113 | public boolean isBrightnessSwipeEnabled() { 114 | return getBoolean(PREF_KEY_SWIPE_BRIGHTNESS, false); 115 | } 116 | 117 | public boolean isDevModeOn() { 118 | return getBoolean(PREF_KEY_DEV_MODE, false); 119 | } 120 | 121 | public boolean isInterceptorOn() { 122 | return getBoolean(PREF_KEY_INTERCEPTOR, false); 123 | } 124 | 125 | public boolean isHideDiscoverTab() { 126 | return getBoolean(PREF_KEY_HIDE_DISCOVER, false); 127 | } 128 | 129 | public boolean isHideEsportsTab() { 130 | return getBoolean(PREF_KEY_HIDE_ESPORTS, false); 131 | } 132 | 133 | public boolean isFloatingChatEnabled() { 134 | return getBoolean(PREF_KEY_FLOATING_CHAT, false); 135 | } 136 | 137 | public EmoteSize getEmoteSize() { 138 | return mEmoteSize; 139 | } 140 | 141 | public UserMessagesFiltering getChatFiltering() { 142 | return mUserMessagesFiltering; 143 | } 144 | 145 | public ExoPlayerSpeed getExoplayerSpeed() { 146 | return (ExoPlayerSpeed) getOrDefault(ExoPlayerSpeed.DEFAULT); 147 | } 148 | 149 | private PreferenceItem getOrDefault(PreferenceItem preferenceItem) { 150 | return preferenceItem.getPreference(getString(preferenceItem.getPreferenceKey(), preferenceItem.getPreferenceValue())); 151 | } 152 | 153 | public MiniPlayerSize getMiniPlayerSize() { 154 | return (MiniPlayerSize) getOrDefault(MiniPlayerSize.DEFAULT); 155 | } 156 | 157 | public FollowView getFollowView() { 158 | return (FollowView) getOrDefault(FollowView.AUTO); 159 | } 160 | 161 | public EmotePickerView getEmotePickerView() { 162 | return (EmotePickerView) getOrDefault(EmotePickerView.AUTO); 163 | } 164 | 165 | public Gifs getGifsStrategy() { 166 | return mGifsStrategy; 167 | } 168 | 169 | public void setEmoteSize(EmoteSize emoteSize) { 170 | mEmoteSize = emoteSize; 171 | } 172 | 173 | public void setChatFiltering(UserMessagesFiltering userMessagesFiltering) { 174 | mUserMessagesFiltering = userMessagesFiltering; 175 | } 176 | 177 | public boolean isDarkThemeEnabled() { 178 | return isDarkThemeEnabled; 179 | } 180 | 181 | public void setGifs(Gifs gifs) { 182 | mGifsStrategy = gifs; 183 | } 184 | 185 | private void initLocalPreferences() { 186 | mEmoteSize = (EmoteSize) getOrDefault(EmoteSize.MEDIUM); 187 | mGifsStrategy = (Gifs) getOrDefault(Gifs.STATIC); 188 | mUserMessagesFiltering = (UserMessagesFiltering) getOrDefault(UserMessagesFiltering.DISABLED); 189 | isDarkThemeEnabled = getBoolean(PREF_KEY_TWITCH_DARK_THEME, false); 190 | } 191 | 192 | @Override 193 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String preferenceKey) { 194 | if (sharedPreferences == null || TextUtils.isEmpty(preferenceKey)) { 195 | Logger.error("null"); 196 | return; 197 | } 198 | 199 | switch (preferenceKey) { 200 | case PrefManager.PREF_KEY_EMOTE_SIZE: 201 | setEmoteSize((EmoteSize) getOrDefault(EmoteSize.MEDIUM)); 202 | break; 203 | case PrefManager.PREF_KEY_GIFS: 204 | setGifs((Gifs) getOrDefault(Gifs.STATIC)); 205 | break; 206 | case PrefManager.PREF_KEY_USER_MESSAGES_FILTERING: 207 | setChatFiltering((UserMessagesFiltering) getOrDefault(UserMessagesFiltering.DISABLED)); 208 | case PrefManager.PREF_KEY_TWITCH_DARK_THEME: 209 | isDarkThemeEnabled = sharedPreferences.getBoolean(PREF_KEY_TWITCH_DARK_THEME, false); 210 | default: 211 | Logger.debug(preferenceKey); 212 | break; 213 | } 214 | } 215 | 216 | public synchronized void updateBoolean(String key, boolean val) { 217 | if (mPref == null) { 218 | Logger.error("mPref is null"); 219 | return; 220 | } 221 | 222 | if (TextUtils.isEmpty(key)) { 223 | Logger.error("Empty key"); 224 | return; 225 | } 226 | 227 | mPref.edit().putBoolean(key, val).apply(); 228 | } 229 | 230 | public synchronized void updateString(String key, String val) { 231 | if (mPref == null) { 232 | Logger.error("mPref is null"); 233 | return; 234 | } 235 | 236 | if (TextUtils.isEmpty(key)) { 237 | Logger.error("Empty key"); 238 | return; 239 | } 240 | 241 | if (TextUtils.isEmpty(val)) { 242 | Logger.error("Empty val"); 243 | return; 244 | } 245 | 246 | mPref.edit().putString(key, val).apply(); 247 | } 248 | 249 | public boolean getBoolean(String key, boolean def) { 250 | if (mPref == null) { 251 | Logger.warning("mPref is null"); 252 | return def; 253 | } 254 | 255 | return mPref.getBoolean(key, def); 256 | } 257 | 258 | public String getString(String key, String def) { 259 | if (mPref == null) { 260 | Logger.warning("mPref is null"); 261 | return def; 262 | } 263 | 264 | return mPref.getString(key, def); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/settings/SettingsController.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.settings; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.view.View; 6 | import android.widget.ArrayAdapter; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import tv.twitch.android.mod.bridges.IDPub; 13 | import tv.twitch.android.mod.bridges.LoaderLS; 14 | import tv.twitch.android.mod.models.settings.EmotePickerView; 15 | import tv.twitch.android.mod.models.settings.FollowView; 16 | import tv.twitch.android.mod.models.settings.UserMessagesFiltering; 17 | import tv.twitch.android.mod.models.settings.EmoteSize; 18 | import tv.twitch.android.mod.models.settings.ExoPlayerSpeed; 19 | import tv.twitch.android.mod.models.settings.Gifs; 20 | import tv.twitch.android.mod.models.settings.MiniPlayerSize; 21 | import tv.twitch.android.mod.models.settings.PlayerImpl; 22 | import tv.twitch.android.mod.models.PreferenceItem; 23 | import tv.twitch.android.mod.utils.Helper; 24 | import tv.twitch.android.mod.utils.Logger; 25 | import tv.twitch.android.shared.ui.menus.j; 26 | import tv.twitch.android.shared.ui.menus.l.b; 27 | import tv.twitch.android.shared.ui.menus.m.a; 28 | 29 | 30 | public class SettingsController { 31 | private static final String TELEGRAM_URL = "https://t.me/pubTw"; 32 | private static final String GITHUB_URL = "https://github.com/nopbreak/TwitchMod"; 33 | 34 | 35 | public static void OnToggleEvent(tv.twitch.android.shared.ui.menus.s.b menuItem, boolean isChecked) { 36 | if (menuItem == null) { 37 | Logger.error("menuItem is null"); 38 | return; 39 | } 40 | 41 | j.a prefType = menuItem.t(); 42 | if (prefType == null) { 43 | Logger.error("prefType is null"); 44 | return; 45 | } 46 | 47 | String preferenceKey = prefType.getPreferenceKey(); 48 | if (TextUtils.isEmpty(preferenceKey)) { 49 | Logger.warning("preferenceKey is null. Object=" + prefType.toString()); 50 | return; 51 | } 52 | 53 | LoaderLS.getPrefManagerInstance().updateBoolean(preferenceKey, isChecked); 54 | } 55 | 56 | public static final class OnDropEvent implements a.a1 { 57 | final ArrayAdapter adapter; 58 | 59 | OnDropEvent(ArrayAdapter arrayAdapter) { 60 | this.adapter = arrayAdapter; 61 | } 62 | 63 | @Override 64 | public void a(a menuModel, int itemPos, boolean z) { 65 | PreferenceItem preference = this.adapter.getItem(itemPos); 66 | if (preference == null) { 67 | Logger.error("preference is null"); 68 | return; 69 | } 70 | 71 | Logger.debug("Preference key=" + preference.getPreferenceKey() + ", val=" + preference.getPreferenceValue()); 72 | LoaderLS.getPrefManagerInstance().updateString(preference.getPreferenceKey(), preference.getPreferenceValue()); 73 | } 74 | } 75 | 76 | public static class MenuFactory { 77 | private static tv.twitch.android.shared.ui.menus.l.b getMenuInfo(String title, String desc, View.OnClickListener listener) { 78 | return new tv.twitch.android.shared.ui.menus.o.a(title, desc, null, null, null, null, listener, 0, null); 79 | } 80 | 81 | private static tv.twitch.android.shared.ui.menus.l.b getToggleMenu(String title, String desc, boolean state, j.a controller) { 82 | return new tv.twitch.android.shared.ui.menus.s.b(title, desc, null, state, false, null, false, null, false, null, null, null, controller, null, 12276, null); 83 | } 84 | 85 | private static tv.twitch.android.shared.ui.menus.l.b getDropDownMenu(Context context, String title, String desc, ArrayList list, PreferenceItem state) { 86 | ArrayAdapter adapter = new ArrayAdapter<>(context, tv.twitch.android.settings.d.twitch_spinner_dropdown_item, list); 87 | return new tv.twitch.android.shared.ui.menus.m.a<>(adapter, list.indexOf(state), title, desc, null, null, new OnDropEvent(adapter)); 88 | } 89 | } 90 | 91 | public static void initialize(Context context, List items) { 92 | items.clear(); 93 | 94 | PrefManager prefManager = LoaderLS.getPrefManagerInstance(); 95 | IDPub idPub = LoaderLS.getIDPubInstance(); 96 | 97 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_settings_chat_bttv"), null, null)); 98 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_bttv_settings_bttv_emotes"), idPub.getString("mod_bttv_settings_bttv_emotes_desc"), prefManager.isEmotesOn(), j.a.BttvEmotes)); 99 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_bttv_settings_bttv_widget"), idPub.getString("mod_bttv_settings_bttv_widget_desc"), prefManager.isHookEmoticonSetOn(), j.a.BttvEmotesPicker)); 100 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_bttv_settings_bttv_gifs"), idPub.getString("mod_bttv_settings_bttv_gifs_desc"), new ArrayList(Arrays.asList(Gifs.values())), prefManager.getGifsStrategy())); 101 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_bttv_settings_emote_size"), idPub.getString("mod_bttv_settings_emote_size_desc"), new ArrayList(Arrays.asList(EmoteSize.values())), prefManager.getEmoteSize())); 102 | 103 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_settings_chat_category"), null, null)); 104 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_chat_settings_filtering"), idPub.getString("mod_chat_settings_filtering_desc"), new ArrayList(Arrays.asList(UserMessagesFiltering.values())), prefManager.getChatFiltering())); 105 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_chat_settings_emote_picker"), null, new ArrayList(Arrays.asList(EmotePickerView.values())), prefManager.getEmotePickerView())); 106 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_chat_settings_floating_chat"), idPub.getString("mod_chat_settings_floating_chat_desc"), prefManager.isFloatingChatEnabled(), j.a.FloatingChat)); 107 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_chat_settings_tap_copy"), idPub.getString("mod_chat_settings_tap_copy_desc"), prefManager.isCopyMsgOn(), j.a.CopyMsg)); 108 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_chat_settings_timestamps"), idPub.getString("mod_chat_settings_timestamps_desc"), prefManager.isTimestampsOn(), j.a.Timestamps)); 109 | 110 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_settings_player_category"), null, null)); 111 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_player_settings_disable_autoplay"), idPub.getString("mod_player_settings_disable_autoplay_desc"), prefManager.isDisableAutoplay(), j.a.AutoPlay)); 112 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_player_settings_video_debug_panel"), idPub.getString("mod_player_settings_video_debug_panel_desc"), prefManager.isShowVideoDebugPanel(), j.a.VideoDebugPanel)); 113 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_player_settings_follow_view"), null, new ArrayList(Arrays.asList(FollowView.values())), prefManager.getFollowView())); 114 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_player_settings_player"), idPub.getString("mod_player_settings_player_desc"), new ArrayList(Arrays.asList(PlayerImpl.values())), prefManager.getPlayer())); 115 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_player_settings_miniplayer_size"), idPub.getString("mod_player_settings_miniplayer_size_desc"), new ArrayList(Arrays.asList(MiniPlayerSize.values())), prefManager.getMiniPlayerSize())); 116 | items.add(MenuFactory.getDropDownMenu(context, idPub.getString("mod_player_settings_exoplayer_speed"), idPub.getString("mod_player_settings_exoplayer_speed_desc"), new ArrayList(Arrays.asList(ExoPlayerSpeed.values())), prefManager.getExoplayerSpeed())); 117 | 118 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_settings_swipe"), null, null)); 119 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_swipe_settings_enable_volume"), idPub.getString("mod_swipe_settings_enable_volume_desc"), prefManager.isVolumeSwipeEnabled(), j.a.VolumeSwipe)); 120 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_swipe_settings_enable_brightness"), idPub.getString("mod_swipe_settings_enable_brightness_desc"), prefManager.isBrightnessSwipeEnabled(), j.a.BrightnessSwipe)); 121 | 122 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_settings_patch"), null, null)); 123 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_disable_recommendations"), idPub.getString("mod_patches_settings_disable_recommendations_desc"), prefManager.isDisableRecommendations(), j.a.HideRecommendedStreams)); 124 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_disable_resume_watching"), idPub.getString("mod_patches_settings_disable_resume_watching_desc"), prefManager.isDisableRecentWatching(), j.a.HideResumeWatchingStreams)); 125 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_disable_followed_games"), idPub.getString("mod_patches_settings_disable_followed_games_desc"), prefManager.isDisableFollowedGames(), j.a.HideFollowedGames)); 126 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_hide_discover"), idPub.getString("mod_patches_settings_hide_discover_desc"), prefManager.isHideDiscoverTab(), j.a.HideDiscoverTab)); 127 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_hide_esports"), idPub.getString("mod_patches_settings_hide_esports_desc"), prefManager.isHideEsportsTab(), j.a.HideEsportsTab)); 128 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_disable_recent_search"), idPub.getString("mod_patches_settings_disable_recent_search_desc"), prefManager.isDisableRecentSearch(), j.a.RecentSearch)); 129 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_dev_mode"), idPub.getString("mod_patches_settings_dev_mode_desc"), prefManager.isDevModeOn(), j.a.DevMode)); 130 | if (prefManager.isDevModeOn()) { 131 | items.add(MenuFactory.getToggleMenu(idPub.getString("mod_patches_settings_interceptor"), null, prefManager.isInterceptorOn(), j.a.Interceptor)); 132 | } 133 | 134 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_category_info"), null, null)); 135 | 136 | items.add(MenuFactory.getMenuInfo(LoaderLS.VERSION, LoaderLS.BUILD, null)); 137 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_info_open_telegram"), null, new View.OnClickListener() { 138 | @Override 139 | public void onClick(View v) { 140 | Helper.openUrl(TELEGRAM_URL); 141 | } 142 | })); 143 | items.add(MenuFactory.getMenuInfo(idPub.getString("mod_info_open_github"), null, new View.OnClickListener() { 144 | @Override 145 | public void onClick(View v) { 146 | Helper.openUrl(GITHUB_URL); 147 | } 148 | })); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/swipper/Swipper.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.swipper; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.media.AudioManager; 6 | import android.os.Handler; 7 | import android.view.GestureDetector; 8 | import android.view.Gravity; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.RelativeLayout; 13 | 14 | import tv.twitch.android.mod.swipper.util.BrightnessHelper; 15 | import tv.twitch.android.mod.swipper.view.SwipperOverlay; 16 | 17 | 18 | public class Swipper implements GestureDetector.OnGestureListener { 19 | private final static int DELAY_TIMEOUT = 500; 20 | private final static float H = 0.66f; 21 | 22 | private final SwipperOverlay mSwipperOverlay; 23 | private final GestureDetector mGestureDetector; 24 | 25 | private final Activity mContext; 26 | private final AudioManager mAudioManager; 27 | 28 | private final Handler mHandler; 29 | private Runnable mProgressHide; 30 | 31 | private boolean bIsLeftArea = false; 32 | 33 | private int mOldVolume; 34 | private int mOldBrightness; 35 | 36 | private boolean bIsVolumeSwipeEnabled = false; 37 | private boolean bIsBrightnessSwipeEnabled = false; 38 | 39 | 40 | public Swipper(Activity activity) { 41 | mContext = activity; 42 | mHandler = new Handler(); 43 | 44 | mGestureDetector = new GestureDetector(mContext, this); 45 | mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); 46 | 47 | mSwipperOverlay = new SwipperOverlay(mContext); 48 | } 49 | 50 | public void setOverlay(ViewGroup viewGroup) { 51 | bIsVolumeSwipeEnabled = false; 52 | bIsBrightnessSwipeEnabled = false; 53 | 54 | RelativeLayout.LayoutParams overlayParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 55 | 56 | RelativeLayout relativeLayout = new RelativeLayout(mContext); 57 | relativeLayout.setGravity(Gravity.CENTER); 58 | relativeLayout.addView(mSwipperOverlay); 59 | 60 | mSwipperOverlay.setMaxVolume(getSystemMaxVolume()); 61 | mSwipperOverlay.setVolume(getSystemVolume()); 62 | mSwipperOverlay.setBrightness(BrightnessHelper.getWindowBrightness(mContext)); 63 | 64 | viewGroup.addView(relativeLayout, overlayParams); 65 | mSwipperOverlay.setVisibility(View.VISIBLE); 66 | mSwipperOverlay.requestLayout(); 67 | } 68 | 69 | public boolean isEnabled() { 70 | return bIsVolumeSwipeEnabled || bIsBrightnessSwipeEnabled; 71 | } 72 | 73 | public boolean onTouchEvent(MotionEvent motionEvent) { 74 | if (!isEnabled()) 75 | return false; 76 | 77 | switch (motionEvent.getActionMasked()) { 78 | case MotionEvent.ACTION_UP: 79 | case MotionEvent.ACTION_CANCEL: 80 | delayHide(); 81 | } 82 | 83 | return mGestureDetector.onTouchEvent(motionEvent); 84 | } 85 | 86 | private void delayHide() { 87 | if (mProgressHide != null) { 88 | mHandler.removeCallbacks(mProgressHide); 89 | mProgressHide = null; 90 | } 91 | 92 | mProgressHide = new Runnable() { 93 | @Override 94 | public void run() { 95 | hideAll(); 96 | } 97 | }; 98 | 99 | mHandler.postDelayed(mProgressHide, DELAY_TIMEOUT); 100 | } 101 | 102 | @Override 103 | public boolean onDown(MotionEvent e) { 104 | mOldBrightness = BrightnessHelper.getWindowBrightness(mContext); 105 | mOldVolume = getSystemVolume(); 106 | bIsLeftArea = e.getX() < mSwipperOverlay.getWidth() / 2.0f; 107 | 108 | return true; 109 | } 110 | 111 | @Override 112 | public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) { 113 | final float diff = downEvent.getY() - moveEvent.getY(); 114 | 115 | if (bIsLeftArea) { 116 | if (!bIsBrightnessSwipeEnabled) 117 | return false; 118 | 119 | updateBrightnessProgress(diff); 120 | } else { 121 | if (!bIsVolumeSwipeEnabled) 122 | return false; 123 | 124 | updateVolumeProgress(diff); 125 | } 126 | 127 | return true; 128 | } 129 | 130 | public void enableVolumeSwipe() { 131 | bIsVolumeSwipeEnabled = true; 132 | } 133 | 134 | public void enableBrightnessSwipe() { 135 | bIsBrightnessSwipeEnabled = true; 136 | } 137 | 138 | public int getSystemMaxVolume() { 139 | return mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 140 | } 141 | 142 | public int getSystemVolume() { 143 | return mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 144 | } 145 | 146 | public void setSystemVolume(int index) { 147 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); 148 | } 149 | 150 | private int getOverlayHeight() { 151 | return mSwipperOverlay.getHeight(); 152 | } 153 | 154 | private int getMaxVolume() { 155 | return mSwipperOverlay.getMaxVolume(); 156 | } 157 | 158 | private int getMaxBrightness() { 159 | return mSwipperOverlay.getMaxBrightness(); 160 | } 161 | 162 | private int calculate(float delta, int oldStep, int max) { 163 | float height = getOverlayHeight() * H; 164 | 165 | float step = height / max; 166 | int diff = (int) (delta / step); 167 | 168 | return Math.max(0, Math.min(max, oldStep + diff)); 169 | } 170 | 171 | private void updateVolumeProgress(float delta) { 172 | updateVolume(calculate(delta, mOldVolume, getMaxVolume())); 173 | } 174 | 175 | private void updateBrightnessProgress(float delta) { 176 | updateBrightness(calculate(delta, mOldBrightness, getMaxBrightness())); 177 | } 178 | 179 | private void updateBrightness(int val) { 180 | BrightnessHelper.setWindowBrightness(mContext, val); 181 | mSwipperOverlay.setBrightness(val); 182 | mSwipperOverlay.showBrightness(); 183 | } 184 | 185 | public void updateVolume(int index) { 186 | setSystemVolume(index); 187 | mSwipperOverlay.setVolume(index); 188 | mSwipperOverlay.showVolume(); 189 | } 190 | 191 | public void hideAll() { 192 | if (mProgressHide != null) { 193 | mHandler.removeCallbacks(mProgressHide); 194 | mProgressHide = null; 195 | } 196 | 197 | mSwipperOverlay.hideVolume(); 198 | mSwipperOverlay.hideBrightness(); 199 | } 200 | 201 | @Override 202 | public void onShowPress(MotionEvent e) {} 203 | 204 | @Override 205 | public void onLongPress(MotionEvent e) {} 206 | 207 | @Override 208 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 209 | return false; 210 | } 211 | 212 | @Override 213 | public boolean onSingleTapUp(MotionEvent e) { 214 | return false; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/swipper/util/BrightnessHelper.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.swipper.util; 2 | 3 | import android.app.Activity; 4 | import android.view.WindowManager; 5 | 6 | public class BrightnessHelper { 7 | public static int getWindowBrightness(Activity context) { 8 | WindowManager.LayoutParams layoutParams = context.getWindow().getAttributes(); 9 | 10 | return Math.max((int) (layoutParams.screenBrightness * 100), 0); 11 | } 12 | 13 | public static void setWindowBrightness(Activity context, int val) { 14 | WindowManager.LayoutParams layoutParams = context.getWindow().getAttributes(); 15 | 16 | float brightness = ((float) val) / 100; 17 | if (brightness <= 0) 18 | brightness = 0.01f; 19 | 20 | layoutParams.screenBrightness = brightness; 21 | context.getWindow().setAttributes(layoutParams); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/swipper/util/DimensionConverter.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.swipper.util; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.util.TypedValue; 6 | 7 | public class DimensionConverter { 8 | public static int dipToPix(Context context, int dip) { 9 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 10 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/swipper/view/SwipperOverlay.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.swipper.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.RelativeLayout; 9 | import android.widget.TextView; 10 | 11 | import static tv.twitch.android.mod.swipper.util.DimensionConverter.dipToPix; 12 | 13 | public class SwipperOverlay extends RelativeLayout { 14 | private final static int MAX_BRIGHTNESS = 100; 15 | private final static int PROGRESS_TEXT_SIZE = 45; 16 | private final static float SHADOW_SIZE = 2.0f; 17 | 18 | private final Context mContext; 19 | private final VerticalProgressBar volumeProgressBar; 20 | private final VerticalProgressBar brightnessProgressBar; 21 | private final TextView progress; 22 | 23 | public SwipperOverlay(Context context) { 24 | super(context); 25 | mContext = context; 26 | volumeProgressBar = new VerticalProgressBar(context); 27 | brightnessProgressBar = new VerticalProgressBar(context); 28 | progress = new TextView(context); 29 | initialize(); 30 | } 31 | 32 | private void initialize() { 33 | this.setId(View.generateViewId()); 34 | this.setGravity(Gravity.CENTER_VERTICAL); 35 | this.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 36 | 37 | RelativeLayout.LayoutParams volumeBarParams = new RelativeLayout.LayoutParams(dipToPix(mContext, 14), dipToPix(mContext, 110)); 38 | volumeBarParams.addRule(RelativeLayout.CENTER_VERTICAL); 39 | volumeBarParams.setMargins(dipToPix(mContext, 12), 0, dipToPix(mContext, 10), 0); 40 | volumeProgressBar.setLayoutParams(volumeBarParams); 41 | volumeProgressBar.setVisibility(INVISIBLE); 42 | this.addView(volumeProgressBar, volumeBarParams); 43 | 44 | RelativeLayout.LayoutParams brightnessBarParams = new RelativeLayout.LayoutParams(dipToPix(mContext, 14), dipToPix(mContext, 110)); 45 | brightnessBarParams.addRule(RelativeLayout.CENTER_VERTICAL); 46 | brightnessBarParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 47 | brightnessBarParams.setMargins(dipToPix(mContext, 12), 0, dipToPix(mContext, 10), 0); 48 | brightnessProgressBar.setLayoutParams(brightnessBarParams); 49 | brightnessProgressBar.setMax(MAX_BRIGHTNESS); 50 | brightnessProgressBar.setVisibility(INVISIBLE); 51 | this.addView(brightnessProgressBar, brightnessBarParams); 52 | 53 | progress.setId(View.generateViewId()); 54 | progress.setTextSize(PROGRESS_TEXT_SIZE); 55 | progress.setTextColor(Color.WHITE); 56 | progress.setPadding(5,5,5,5); 57 | progress.setShadowLayer(SHADOW_SIZE, 0.0f, 0.0f, Color.BLACK); 58 | 59 | RelativeLayout.LayoutParams textParam = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 60 | textParam.addRule(RelativeLayout.CENTER_VERTICAL); 61 | textParam.addRule(RelativeLayout.CENTER_HORIZONTAL); 62 | progress.setLayoutParams(textParam); 63 | progress.setVisibility(INVISIBLE); 64 | this.addView(progress, textParam); 65 | } 66 | 67 | public void showVolume() { 68 | if (volumeProgressBar.getVisibility() != VISIBLE) 69 | volumeProgressBar.setVisibility(VISIBLE); 70 | 71 | if (progress.getVisibility() != VISIBLE) 72 | progress.setVisibility(VISIBLE); 73 | } 74 | 75 | public void hideVolume() { 76 | volumeProgressBar.setVisibility(INVISIBLE); 77 | progress.setVisibility(INVISIBLE); 78 | } 79 | 80 | public void setVolume(int index) { 81 | volumeProgressBar.setProgress(index); 82 | progress.setText(Integer.toString(index)); 83 | } 84 | 85 | public void setMaxVolume(int max) { 86 | volumeProgressBar.setMax(max); 87 | } 88 | 89 | public int getMaxVolume() { 90 | return volumeProgressBar.getMax(); 91 | } 92 | 93 | public void showBrightness() { 94 | if (brightnessProgressBar.getVisibility() != VISIBLE) 95 | brightnessProgressBar.setVisibility(VISIBLE); 96 | if (progress.getVisibility() != VISIBLE) 97 | progress.setVisibility(VISIBLE); 98 | } 99 | 100 | public void hideBrightness() { 101 | brightnessProgressBar.setVisibility(INVISIBLE); 102 | progress.setVisibility(INVISIBLE); 103 | } 104 | 105 | public void setBrightness(int index) { 106 | brightnessProgressBar.setProgress(index); 107 | progress.setText(Integer.toString(index)); 108 | } 109 | 110 | public int getMaxBrightness() { 111 | return brightnessProgressBar.getMax(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/swipper/view/VerticalProgressBar.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.swipper.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ClipDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.graphics.drawable.GradientDrawable; 8 | import android.graphics.drawable.LayerDrawable; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.widget.ProgressBar; 12 | 13 | public class VerticalProgressBar extends ProgressBar { 14 | private static final int BACKGROUND_COLOR = Color.WHITE; 15 | private static final int PROGRESS_COLOR = Color.parseColor("#6441A5"); 16 | private static final int BORDER_WIDTH = 1; 17 | 18 | public VerticalProgressBar(Context context) { 19 | super(context,null, android.R.attr.progressBarStyleHorizontal); 20 | this.setProgressDrawable(generateProgressDrawable()); 21 | this.setId(View.generateViewId()); 22 | this.setBackgroundColor(BACKGROUND_COLOR); 23 | } 24 | 25 | private static Drawable generateProgressDrawable() { 26 | GradientDrawable backgroundShape = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[]{BACKGROUND_COLOR, BACKGROUND_COLOR, BACKGROUND_COLOR}); 27 | GradientDrawable progressShape = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, new int[]{PROGRESS_COLOR, PROGRESS_COLOR, PROGRESS_COLOR}); 28 | backgroundShape.setStroke(BORDER_WIDTH, Color.BLACK); 29 | 30 | ClipDrawable progressClip = new ClipDrawable(progressShape, Gravity.BOTTOM, ClipDrawable.VERTICAL); 31 | Drawable[] layers = {backgroundShape, progressClip}; 32 | 33 | return new LayerDrawable(layers); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/ChatFilteringUtil.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | 4 | import tv.twitch.android.mod.models.settings.UserMessagesFiltering; 5 | import tv.twitch.chat.ChatLiveMessage; 6 | import tv.twitch.chat.ChatMessageInfo; 7 | 8 | 9 | public class ChatFilteringUtil { 10 | public enum MessageLevel { 11 | DEFAULT, 12 | SUBS, 13 | MODS, 14 | STAFF, 15 | BROADCASTER, 16 | ACCOUNT, 17 | SYSTEM, 18 | UNKNOWN; 19 | 20 | public boolean isHighOrSame(MessageLevel level) { 21 | if (level == null) 22 | return false; 23 | 24 | return this.ordinal() >= level.ordinal(); 25 | } 26 | } 27 | 28 | public static boolean compare(String str, String str2) { 29 | if (str == null) 30 | return false; 31 | 32 | if (str2 == null) 33 | return false; 34 | 35 | return str.equalsIgnoreCase(str2); 36 | } 37 | 38 | public static boolean filter(ChatLiveMessage liveMessage, String accountName, UserMessagesFiltering filtering) { 39 | if (liveMessage == null || liveMessage.messageInfo == null) 40 | return true; 41 | 42 | ChatFilteringUtil.MessageLevel level = ChatFilteringUtil.getMessageLevel(liveMessage.messageInfo, accountName); 43 | switch (filtering) { 44 | case BROADCASTER: 45 | return level.isHighOrSame(MessageLevel.BROADCASTER); 46 | case MODS: 47 | return level.isHighOrSame(MessageLevel.MODS); 48 | case SUBS: 49 | return level.isHighOrSame(MessageLevel.SUBS); 50 | case PLEBS: 51 | return level.isHighOrSame(MessageLevel.DEFAULT); 52 | } 53 | 54 | return true; 55 | } 56 | 57 | public static MessageLevel getMessageLevel(ChatMessageInfo messageInfo, String account) { 58 | if (messageInfo == null) 59 | return MessageLevel.UNKNOWN; 60 | 61 | if (messageInfo.userMode.system) 62 | return MessageLevel.SYSTEM; 63 | 64 | if (compare(account, messageInfo.userName)) 65 | return MessageLevel.ACCOUNT; 66 | 67 | if (messageInfo.userMode.broadcaster) 68 | return MessageLevel.BROADCASTER; 69 | 70 | if (messageInfo.userMode.staff) 71 | return MessageLevel.STAFF; 72 | 73 | if (messageInfo.userMode.moderator || messageInfo.userMode.globalModerator || messageInfo.userMode.administrator) 74 | return MessageLevel.MODS; 75 | 76 | if (messageInfo.userMode.vip || messageInfo.userMode.subscriber) 77 | return MessageLevel.SUBS; 78 | 79 | return MessageLevel.DEFAULT; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/ChatUtil.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | 4 | import android.graphics.Color; 5 | import android.text.SpannableString; 6 | import android.text.SpannableStringBuilder; 7 | import android.text.Spanned; 8 | import android.text.SpannedString; 9 | import android.text.TextUtils; 10 | import android.text.style.RelativeSizeSpan; 11 | import android.util.LruCache; 12 | 13 | import java.text.SimpleDateFormat; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Locale; 17 | 18 | import tv.twitch.android.mod.bridges.IChatMessageFactory; 19 | import tv.twitch.android.mod.bridges.LoaderLS; 20 | import tv.twitch.android.mod.emotes.EmoteManager; 21 | import tv.twitch.android.mod.models.Emote; 22 | import tv.twitch.android.mod.models.settings.Gifs; 23 | import tv.twitch.android.mod.settings.PrefManager; 24 | import tv.twitch.android.models.chat.MessageToken; 25 | 26 | 27 | public class ChatUtil { 28 | private static final String TIMESTAMP_DATE_FORMAT = "HH:mm "; 29 | 30 | 31 | private static final LruCache mDarkThemeCache = new LruCache(500) { 32 | @Override 33 | protected Integer create(Integer color) { 34 | float[] hsv = new float[3]; 35 | Color.colorToHSV(color, hsv); 36 | if (hsv[2] >= .3) { 37 | return color; 38 | } 39 | 40 | hsv[2] = (float) .4; 41 | return Color.HSVToColor(hsv); 42 | } 43 | }; 44 | 45 | private static final LruCache mWhiteThemeCache = new LruCache(500) { 46 | @Override 47 | protected Integer create(Integer color) { 48 | float[] hsv = new float[3]; 49 | Color.colorToHSV(color, hsv); 50 | if (hsv[2] <= 0.9) { 51 | return color; 52 | } 53 | 54 | hsv[2] = (float) .8; 55 | return Color.HSVToColor(hsv); 56 | } 57 | }; 58 | 59 | public enum EmoteSet { 60 | GLOBAL("-110", "BetterTTV Global Emotes"), 61 | FFZ( "-109", "FFZ Channel Emotes"), 62 | BTTV( "-108", "BetterTTV Channel Emotes"); 63 | 64 | public final String description; 65 | public final String setId; 66 | 67 | public static EmoteSet findById(String id) { 68 | for (EmoteSet set : values()) { 69 | if (set.getId().equals(id)) 70 | return set; 71 | } 72 | 73 | return null; 74 | } 75 | 76 | EmoteSet(String id, String description) { 77 | this.description = description; 78 | this.setId = id; 79 | } 80 | 81 | public String getId() { 82 | return setId; 83 | } 84 | 85 | public String getDescription() { 86 | return description; 87 | } 88 | } 89 | 90 | public static Spanned addTimestamp(Spanned message, Date date) { 91 | final String dateFormat = new SimpleDateFormat(TIMESTAMP_DATE_FORMAT, Locale.UK).format(date); 92 | SpannableString dateString = SpannableString.valueOf(dateFormat); 93 | dateString.setSpan(new RelativeSizeSpan(0.75f), 0, dateString.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 94 | SpannableStringBuilder formatted = new SpannableStringBuilder(dateString); 95 | formatted.append(new SpannableStringBuilder(message)); 96 | return SpannableString.valueOf(formatted); 97 | } 98 | 99 | public static Integer fixUsernameColor(int color) { 100 | if (LoaderLS.getPrefManagerInstance().isDarkThemeEnabled()) { 101 | return mDarkThemeCache.get(color); 102 | } else { 103 | return mWhiteThemeCache.get(color); 104 | } 105 | } 106 | 107 | public static String getRawMessage(List tokens) { 108 | StringBuilder stringBuilder = new StringBuilder(); 109 | for (MessageToken messageToken : tokens) { 110 | if (messageToken instanceof MessageToken.TextToken) 111 | stringBuilder.append(((MessageToken.TextToken) messageToken).getText()); 112 | else if (messageToken instanceof MessageToken.EmoticonToken) 113 | stringBuilder.append(((MessageToken.EmoticonToken) messageToken).getText()); 114 | else if (messageToken instanceof MessageToken.MentionToken) 115 | stringBuilder.append(((MessageToken.MentionToken) messageToken).getText()); 116 | else if (messageToken instanceof MessageToken.UrlToken) 117 | stringBuilder.append(((MessageToken.UrlToken) messageToken).getUrl()); 118 | } 119 | 120 | return stringBuilder.toString(); 121 | } 122 | 123 | public static SpannedString injectEmotesSpan(IChatMessageFactory factory, EmoteManager emoteManager, SpannedString messageSpan, int channelID, PrefManager manager) { 124 | if (TextUtils.isEmpty(messageSpan)) { 125 | Logger.warning("Empty messageSpan"); 126 | return messageSpan; 127 | } 128 | 129 | if (emoteManager == null) { 130 | Logger.error("emoteManager is null"); 131 | return messageSpan; 132 | } 133 | 134 | if (factory == null) { 135 | Logger.error("factory is null"); 136 | return messageSpan; 137 | } 138 | 139 | boolean newWord = false; 140 | int startPos = 0; 141 | int messageLength = messageSpan.length(); 142 | 143 | SpannableStringBuilder ssb = null; 144 | 145 | for (int currentPos = 0; currentPos <= messageLength; currentPos++) { 146 | if (currentPos != messageLength && messageSpan.charAt(currentPos) != ' ') { 147 | if (!newWord) { 148 | newWord = true; 149 | startPos = currentPos; 150 | } 151 | } else { 152 | if (newWord) { 153 | newWord = false; 154 | 155 | String code = String.valueOf(messageSpan.subSequence(startPos, currentPos)); 156 | Emote emote = emoteManager.getEmote(code, channelID); 157 | if (emote != null) { 158 | if (emote.isGif() && manager.getGifsStrategy() == Gifs.DISABLED) 159 | continue; 160 | 161 | SpannableString emoteSpan = (SpannableString) factory.getSpannedEmote(emote.getUrl(manager.getEmoteSize()), emote.getCode()); 162 | if (emoteSpan != null) { 163 | if (ssb == null) 164 | ssb = new SpannableStringBuilder(messageSpan); 165 | 166 | ssb.replace(startPos, currentPos, emoteSpan); 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | if (ssb != null) 174 | return SpannedString.valueOf(ssb); 175 | 176 | return messageSpan; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/Clicker.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | 4 | import android.view.View; 5 | 6 | 7 | public class Clicker implements Runnable { 8 | private final View.OnClickListener mListener; 9 | 10 | public Clicker(View.OnClickListener listener) { 11 | mListener = listener; 12 | } 13 | 14 | @Override 15 | public void run() { 16 | if (mListener != null) { 17 | mListener.onClick(null); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/GifHelper.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.text.Spanned; 5 | import android.text.TextUtils; 6 | import android.widget.TextView; 7 | 8 | import com.bumptech.glide.load.n.g.c; 9 | 10 | import java.util.List; 11 | 12 | import tv.twitch.a.k.e0.b.r.a; 13 | import tv.twitch.android.core.adapters.t; 14 | import tv.twitch.android.mod.bridges.IDrawable; 15 | import tv.twitch.android.mod.bridges.IMessageRecyclerItem; 16 | 17 | 18 | public class GifHelper { 19 | public static void recycleObject(Object item, boolean force) { 20 | if (item == null) 21 | return; 22 | 23 | if (item instanceof IMessageRecyclerItem) { 24 | recycleGifsInText(((IMessageRecyclerItem) item).getSpanned(), force); 25 | } else if (item instanceof TextView) { 26 | recycleGifsInText(((TextView) item).getText(), force); 27 | } else { 28 | Logger.debug("Check item=" + item.toString() + ", " + item.getClass().toString()); 29 | } 30 | } 31 | 32 | public static void recycleGifsInText(CharSequence sequence, boolean force) { 33 | if (TextUtils.isEmpty(sequence)) 34 | return; 35 | 36 | Spanned message = (Spanned) sequence; 37 | 38 | a[] imageSpans = message.getSpans(0, message.length(), a.class); 39 | if (imageSpans.length == 0) 40 | return; 41 | 42 | for (a image: imageSpans) { 43 | if (image == null) 44 | continue; 45 | 46 | Drawable drawable = image.a(); 47 | 48 | if (!(drawable instanceof IDrawable)) 49 | continue; 50 | 51 | IDrawable drawableContainer = (IDrawable) drawable; 52 | 53 | if (drawableContainer.getDrawable() instanceof c) { 54 | c gifDrawable = (c) drawableContainer.getDrawable(); 55 | gifDrawable.stop(); 56 | if (force) 57 | gifDrawable.setCallback(null); 58 | } 59 | } 60 | } 61 | 62 | public static void recycleAdapterItems(List list) { 63 | if (list == null || list.size() == 0) 64 | return; 65 | 66 | for (t item : list) { 67 | recycleObject(item, true); 68 | } 69 | } 70 | 71 | public static void recycleAdapterItems(List list, int range) { 72 | if (range == 0) 73 | return; 74 | 75 | if (list == null || list.size() == 0) 76 | return; 77 | 78 | if (list.size() <= range) { 79 | Logger.debug("Bad range: " + range + " [list size=" + list.size() + "]"); 80 | return; 81 | } 82 | 83 | for (int i = 0; i < range; i++) { 84 | recycleObject(list.get(i), true); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/Helper.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | 4 | import android.content.ClipData; 5 | import android.content.ClipboardManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Handler; 10 | import android.text.TextUtils; 11 | import android.view.View; 12 | import android.widget.Toast; 13 | 14 | import java.util.Locale; 15 | import java.util.Random; 16 | 17 | import tv.twitch.android.api.p1.i1; 18 | import tv.twitch.android.mod.bridges.LoaderLS; 19 | import tv.twitch.android.models.Playable; 20 | import tv.twitch.android.models.clips.ClipModel; 21 | import tv.twitch.android.shared.chat.communitypoints.models.CommunityPointsModel; 22 | 23 | 24 | public class Helper { 25 | private static final int MIN_CLICK_DELAY = 1000; 26 | private static final int MAX_CLICK_DELAY = 5000; 27 | 28 | private final Handler mHandler; 29 | 30 | private int mCurrentChannel = 0; 31 | 32 | public Helper() { 33 | mHandler = new Handler(); 34 | } 35 | 36 | public static void openUrl(String url) { 37 | if (TextUtils.isEmpty(url)) { 38 | Logger.error("Empty url"); 39 | return; 40 | } 41 | 42 | if (!url.startsWith("http://") && !url.startsWith("https://")) 43 | url = "https://" + url; 44 | 45 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 46 | 47 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 48 | LoaderLS.getInstance().startActivity(intent); 49 | } 50 | 51 | public static int getChannelId(i1 playableModelParser, Playable playable) { 52 | if (playableModelParser == null) { 53 | Logger.error("playableModelParser is null"); 54 | return 0; 55 | } 56 | if (playable == null) { 57 | Logger.error("playable is null"); 58 | return 0; 59 | } 60 | 61 | if (playable instanceof ClipModel) { 62 | return ((ClipModel) playable).getBroadcasterId(); 63 | } 64 | 65 | return playableModelParser.a(playable); 66 | } 67 | 68 | public static void showToast(String message) { 69 | Context context = LoaderLS.getInstance().getApplicationContext(); 70 | Toast.makeText(context, message, Toast.LENGTH_LONG).show(); 71 | } 72 | 73 | private static int getClickDelay() { 74 | return new Random().nextInt(MAX_CLICK_DELAY-MIN_CLICK_DELAY) + MIN_CLICK_DELAY; 75 | } 76 | 77 | public void setClicker(View.OnClickListener listener) { 78 | mHandler.postDelayed(new Clicker(listener), getClickDelay()); 79 | } 80 | 81 | public static void saveToClipboard(String text) { 82 | Context context = LoaderLS.getInstance().getApplicationContext(); 83 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 84 | if (clipboard == null) { 85 | Logger.error("clipboard is null"); 86 | return; 87 | } 88 | 89 | clipboard.setPrimaryClip(ClipData.newPlainText("text", text)); 90 | showToast(String.format(Locale.ENGLISH, "«%s» copied to clipboard", text)); 91 | } 92 | 93 | public void setCurrentChannel(int channelID) { 94 | if (channelID < 0) 95 | channelID = 0; 96 | 97 | this.mCurrentChannel = channelID; 98 | } 99 | 100 | public static boolean checkStackTrace(String clazz) { 101 | if (TextUtils.isEmpty(clazz)) { 102 | Logger.error("Empty clazz"); 103 | return false; 104 | } 105 | 106 | for (StackTraceElement element : Thread.currentThread().getStackTrace()) { 107 | if (element == null) 108 | continue; 109 | 110 | if (TextUtils.isEmpty(element.getClassName())) 111 | continue; 112 | 113 | if (!element.getClassName().equalsIgnoreCase(clazz)) 114 | continue; 115 | 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | public int getCurrentChannel() { 123 | return this.mCurrentChannel; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /mod/src/main/java/tv/twitch/android/mod/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.mod.utils; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import tv.twitch.android.mod.bridges.LoaderLS; 7 | 8 | 9 | public class Logger { 10 | public static void error(String msg) { 11 | Log.e(getTag(), msg); 12 | } 13 | 14 | public static void warning(String msg) { 15 | Log.w(getTag(), msg); 16 | } 17 | 18 | public static void info(String msg) { 19 | Log.i(getTag(), msg); 20 | } 21 | 22 | public static void debug(String msg) { 23 | Log.d(getTag(), msg); 24 | } 25 | 26 | private static String getTag() { 27 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4]; 28 | String className = stackTraceElement.getClassName(); 29 | String methodName = stackTraceElement.getMethodName(); 30 | return className + "->" + methodName; 31 | } 32 | 33 | public static void showDebugToast(String msg) { 34 | if (LoaderLS.getInstance() == null) { 35 | Logger.error("instance is null"); 36 | return; 37 | } 38 | 39 | Logger.debug("Toast message: " + msg); 40 | if (!LoaderLS.getPrefManagerInstance().isDevModeOn()) 41 | return; 42 | 43 | Helper.showToast(msg); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mod/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BetterTTV/FFZ 4 | Включить 5 | Интегрировать в «Меню смайлов» 6 | Отображение GIF анимаций 7 | Исходный размер смайлов 8 | 9 | Чат 10 | Плавающий чат 11 | Копировать текст сообщения на долгий тап 12 | Показывать время отправки сообщения 13 | 14 | Плеер 15 | Отключить рекомендации 16 | Включить статистику 17 | Выбор плеера 18 | Размер мини-плеера 19 | Скорость воспроизведения 20 | 21 | Настройка свайпов 22 | Громкость 23 | Яркость 24 | 25 | Патчи 26 | Скрыть «Рекомендуемые каналы» 27 | Скрыть «Продолжить просмотр» 28 | Скрыть «Отслеживаемые категории» 29 | Скрыть «Поиск» 30 | Скрыть «Киберспорт» 31 | Отключить историю недавнего поиска 32 | Режим разработчика 33 | 34 | Инфо 35 | Телеграм 36 | Исходный код 37 | -------------------------------------------------------------------------------- /mod/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BetterTTV/FFZ 4 | Enable 5 | Show BetterTTV/FFZ emoticons in chat 6 | Emote menu 7 | Adds extra BetterTTV/FFZ emoticons to emote picker 8 | 9 | GIFS 10 | Change the mode of how GIF emoticons are showing up 11 | Source emote size 12 | 13 | 14 | Chat 15 | Emote picker view 16 | Floating chat 17 | Enable floating chat over video in landscape mode 18 | Message user-level filtering 19 | Messages will be removed from chat entirely if they aren\'t sent by a user of this level or higher 20 | Tap copy 21 | Tap and hold on the message to copy 22 | Timestamps 23 | Display timestamps alongside chat messages 24 | 25 | Player 26 | Turn off autoplay 27 | Disable autoplay of the next video 28 | Follow button view 29 | Video stats 30 | Enable video debug information 31 | Player implementation 32 | 33 | MiniPlayer size 34 | 35 | ExoPlayer VOD speed 36 | 37 | 38 | Swipe controls 39 | Volume swipe 40 | Control volume by swiping 41 | Brightness swipe 42 | Control brightness by swiping 43 | 44 | Patches 45 | Hide «Recommended Streams» 46 | 47 | Hide «Recent Watching» 48 | 49 | Hide «Followed Games» 50 | 51 | Hide «Discover» tab 52 | You must restart the app to apply changes 53 | Hide «ESports» tab 54 | You must restart the app to apply changes 55 | Disable recent searches 56 | Hide all your recent searches 57 | Developer mode 58 | This option should only be enable if you think you need it 59 | Enable interceptor 60 | 61 | Info 62 | Open Telegram channel 63 | Open GitHub page 64 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':mod', ':libwrapper', ':twitch' 2 | rootProject.name='TwitchMod' 3 | -------------------------------------------------------------------------------- /twitch/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /twitch/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode 901010 10 | versionName "9.1.1" 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | buildToolsVersion = '29.0.3' 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | } 25 | -------------------------------------------------------------------------------- /twitch/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrozenAlex/TwitchMod/171f533bf96edc47e491beefa9d50d4ea7d2c059/twitch/consumer-rules.pro -------------------------------------------------------------------------------- /twitch/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /twitch/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /twitch/src/main/java/androidx/fragment/app/FragmentActivity.java: -------------------------------------------------------------------------------- 1 | package androidx.fragment.app; 2 | 3 | import android.app.Activity; 4 | 5 | public class FragmentActivity extends Activity { 6 | } 7 | -------------------------------------------------------------------------------- /twitch/src/main/java/com/bumptech/glide/load/n/g/c.java: -------------------------------------------------------------------------------- 1 | package com.bumptech.glide.load.n.g; 2 | 3 | 4 | import android.graphics.Canvas; 5 | import android.graphics.ColorFilter; 6 | import android.graphics.drawable.Drawable; 7 | 8 | 9 | /** 10 | * Source: GifDrawable 11 | * https://bumptech.github.io/glide/javadocs/440/com/bumptech/glide/load/resource/gif/GifDrawable.html 12 | */ 13 | public class c extends Drawable { 14 | @Override 15 | public void draw(Canvas canvas) {} 16 | 17 | @Override 18 | public void setAlpha(int alpha) {} 19 | 20 | @Override 21 | public void setColorFilter(ColorFilter colorFilter) {} 22 | 23 | @Override 24 | public int getOpacity() { 25 | return 0; 26 | } 27 | 28 | public void start() {} 29 | 30 | public void stop() {} 31 | } 32 | -------------------------------------------------------------------------------- /twitch/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java: -------------------------------------------------------------------------------- 1 | package com.google.android.exoplayer2; 2 | 3 | /** 4 | * https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/PlaybackParameters.html 5 | */ 6 | public class PlaybackParameters { 7 | public static final PlaybackParameters e = new PlaybackParameters(1.0f); 8 | public final float a; 9 | 10 | 11 | public PlaybackParameters(float f) { 12 | a = f; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /twitch/src/main/java/kotlin/jvm/c/g.java: -------------------------------------------------------------------------------- 1 | package kotlin.jvm.c; 2 | 3 | /** 4 | * Source: DefaultConstructorMarker 5 | */ 6 | public class g { 7 | } 8 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/b/i/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.b.i; 2 | 3 | /** 4 | * Source: DynamicContentFetcher 5 | */ 6 | public class d { 7 | private boolean e = true; 8 | 9 | 10 | public boolean x() { // TODO: __REMOVE_FINAL 11 | return this.e; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/b/i/e.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.b.i; 2 | 3 | /** 4 | * Source: NoParamDynamicContentFetcher 5 | */ 6 | public class e extends d {} 7 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/e0/b/r/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.e0.b.r; 2 | 3 | 4 | import android.graphics.drawable.Drawable; 5 | import android.text.style.ImageSpan; 6 | 7 | 8 | /** 9 | * Source: CenteredImageSpan 10 | */ 11 | public class a extends ImageSpan { 12 | private final Drawable b = null; 13 | 14 | public a(Drawable drawable) { 15 | super(drawable); 16 | } 17 | 18 | public final Drawable a() { 19 | return b; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/e0/b/r/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.e0.b.r; 2 | 3 | /** 4 | * Source: MediaSpan 5 | */ 6 | public enum d { 7 | c(24.0f), // Emote 8 | d(18.0f), // Badge 9 | e(18.0f), // Reward 10 | f(24.0f); // AnimatedBit 11 | 12 | 13 | d(float size) {} 14 | } 15 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/g/d1/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.d1; 2 | 3 | import tv.twitch.android.models.channel.ChannelInfo; 4 | 5 | /** 6 | * Source: ChatEvents 7 | */ 8 | public class b { 9 | public final ChannelInfo a() { 10 | return null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/g/g.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g; 2 | 3 | import java.util.List; 4 | 5 | import tv.twitch.android.models.chat.MessageToken; 6 | 7 | /** 8 | * Source: ChatMessageInterface 9 | */ 10 | public interface g { 11 | boolean a(); 12 | 13 | /** 14 | * @return Username 15 | */ 16 | String b(); 17 | 18 | boolean c(); 19 | 20 | /** 21 | * @return User ID 22 | */ 23 | int d(); 24 | 25 | List e(); 26 | 27 | List f(); 28 | 29 | boolean g(); 30 | 31 | String getDisplayName(); 32 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/g/i1/e.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g.i1; 2 | 3 | /** 4 | * Source: ChatMessageSpanGroup 5 | */ 6 | public class e { 7 | public static final e d = new e(null, null, ""); 8 | 9 | public e(CharSequence charSequence, CharSequence charSequence2, CharSequence charSequence3) {} 10 | } 11 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/g/n0.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.g; 2 | 3 | import android.text.style.ClickableSpan; 4 | 5 | /** 6 | * Source: UrlImageClickableProvider 7 | */ 8 | public interface n0 { 9 | ClickableSpan a(String str); 10 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/l/h/i/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.l.h.i; 2 | 3 | /** 4 | * Source: EmoteHeaderUiModel 5 | */ 6 | public class a { 7 | public static final class b extends a { 8 | private final int a; 9 | private final boolean b; 10 | private final boolean c; 11 | 12 | 13 | public b(int title, boolean isTopBorderVisible, boolean isProfileUrlVisible) { 14 | this.a = title; 15 | this.b = isTopBorderVisible; 16 | this.c = isProfileUrlVisible; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/l/h/i/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.l.h.i; 2 | 3 | /** 4 | * Source: EmoteUiModel 5 | */ 6 | public class b { 7 | } 8 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/l/h/i/c.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.l.h.i; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Source: EmoteUiSet 7 | */ 8 | public class c { 9 | private final a a; 10 | private final List b; 11 | 12 | public c(a header, List emotes) { 13 | this.a = header; 14 | this.b = emotes; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/a/k/m/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.a.k.m; 2 | 3 | /** 4 | * Source: Experiment 5 | */ 6 | public enum a { 7 | G0, // NEW_EMOTE_PICKER 8 | m0, // GLOBAL_FOLLOW_BUTTON_REVAMP 9 | } 10 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/api/p1/i1.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.api.p1; 2 | 3 | import tv.twitch.android.models.Playable; 4 | 5 | /** 6 | * Source: PlayableModelParser 7 | */ 8 | public class i1 { 9 | /** 10 | * @return Channel ID 11 | */ 12 | public final int a(Playable playable) { 13 | return 0; 14 | } 15 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/app/consumer/TwitchApplication.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.app.consumer; 2 | 3 | import android.app.Application; 4 | 5 | public class TwitchApplication extends Application { 6 | public void onCreate() { 7 | super.onCreate(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/core/adapters/t.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.core.adapters; 2 | 3 | 4 | /** 5 | * Source: RecyclerAdapterItem 6 | */ 7 | public class t { 8 | } 9 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/models/Playable.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models; 2 | 3 | 4 | public interface Playable {} 5 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/models/channel/ChannelInfo.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models.channel; 2 | 3 | 4 | public interface ChannelInfo { 5 | String getDisplayName(); 6 | 7 | String getGame(); 8 | 9 | int getId(); 10 | 11 | String getName(); 12 | 13 | boolean isPartner(); 14 | 15 | boolean isRecommendation(); 16 | 17 | void setRecommendation(boolean z); 18 | } 19 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/models/chat/MessageToken.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models.chat; 2 | 3 | 4 | public abstract class MessageToken { 5 | public static final class EmoticonToken extends MessageToken { 6 | public final String getText() { 7 | return ""; 8 | } 9 | } 10 | 11 | public static final class MentionToken extends MessageToken { 12 | public final String getText() { 13 | return ""; 14 | } 15 | } 16 | 17 | public static final class TextToken extends MessageToken { 18 | public final String getText() { 19 | return ""; 20 | } 21 | } 22 | 23 | public static final class UrlToken extends MessageToken { 24 | public final String getUrl() { 25 | return ""; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/models/clips/ClipModel.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models.clips; 2 | 3 | 4 | import tv.twitch.android.models.Playable; 5 | 6 | 7 | public class ClipModel implements Playable { 8 | public int getBroadcasterId() { 9 | return 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/models/communitypoints/ActiveClaimModel.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.models.communitypoints; 2 | 3 | 4 | public class ActiveClaimModel { 5 | private final String id = ""; 6 | 7 | 8 | public final String getId() { 9 | return this.id; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/settings/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.settings; 2 | 3 | public class d { 4 | public static final int twitch_spinner_dropdown_item = 2131624491; 5 | } 6 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/settings/l/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.settings.l; 2 | 3 | 4 | import androidx.fragment.app.FragmentActivity; 5 | 6 | import java.util.List; 7 | 8 | import tv.twitch.android.shared.ui.menus.j; 9 | 10 | /** 11 | * Source: BaseSettingsPresenter 12 | */ 13 | public abstract class b { 14 | 15 | 16 | /** 17 | * Destinations 18 | */ 19 | protected abstract d U1(); 20 | 21 | /** 22 | * Title 23 | */ 24 | public abstract String X1(); 25 | 26 | 27 | /** 28 | * Controller 29 | */ 30 | protected abstract j V1(); 31 | 32 | public final FragmentActivity R1() { 33 | return null; 34 | } 35 | 36 | public abstract void c2(); 37 | 38 | /** 39 | * Menu items 40 | */ 41 | public final List W1() { 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/settings/l/d.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.settings.l; 2 | 3 | /** 4 | * Source: SettingsNavigationController 5 | */ 6 | public interface d { 7 | } 8 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/chat/communitypoints/models/CommunityPointsModel.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.chat.communitypoints.models; 2 | 3 | import tv.twitch.android.models.communitypoints.ActiveClaimModel; 4 | 5 | public class CommunityPointsModel { 6 | public final ActiveClaimModel getClaim() { 7 | return null; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/j.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus; 2 | 3 | import tv.twitch.android.shared.ui.menus.k.b; 4 | 5 | public interface j { 6 | enum a { // TODO: __REPLACE_CLASS 7 | b(null), // 0 8 | SmartFeed(null), // 1 9 | c(null), // 2 10 | d(null), // 3 11 | InAppWhispers( null), // 4 12 | InAppFriendRequests( null), // 5 13 | e(null), // 6 14 | f( null), // 7 15 | g(null), // 8 16 | BackgroundAudio(null), // 9 17 | h( null), // 10 18 | i(null), // 11 19 | j( null), // 12 20 | k(null), // 13 21 | l(null), // 14 22 | ShowActivityFeedResubs(null), // 15 23 | m( null), // 16 24 | n( null), // 17 25 | o(null), // 18 26 | p( null), // 19 27 | q(null), // 20 28 | r( null), // 21 29 | s(null), // 22 30 | t( null), // 23 31 | u( null), // 24 32 | v( null), // 25 33 | w( null), // 26 34 | x( null), // 27 35 | y( null), // 28 36 | z(null), // 29 37 | A( null), // 30 38 | B( null), // 31 39 | C(null), // 32 40 | D(null), // 33 41 | E( null), // 34 42 | GameBroadcastingViewerCount( null), // 35 43 | BttvEmotes( "MOD_EMOTES"), 44 | BttvEmotesPicker( "MOD_EMOTE_PICKER"), 45 | FloatingChat("MOD_FLOATING_CHAT"), 46 | Timestamps( "MOD_TIMESTAMPS"), 47 | VideoDebugPanel( "MOD_VIDEO_DEBUG"), 48 | VolumeSwipe("MOD_SWIPE_VOLUME"), 49 | BrightnessSwipe( "MOD_SWIPE_BRIGHTNESS"), 50 | HideRecommendedStreams("MOD_DISABLE_RECOMMENDATIONS"), 51 | HideResumeWatchingStreams( "MOD_DISABLE_RESUME_WATCHING"), 52 | HideFollowedGames("MOD_DISABLE_FOLLOWED_GAMES"), 53 | HideDiscoverTab( "MOD_HIDE_NAVIGATION_DISCOVER"), 54 | HideEsportsTab("MOD_HIDE_NAVIGATION_ESPORTS"), 55 | RecentSearch( "MOD_DISABLE_RECENT_SEARCH"), 56 | DevMode( "MOD_DEV_MOD"), 57 | AutoPlay("MOD_DISABLE_AUTOPLAY"), 58 | CopyMsg( "MOD_COPY_MSG"), 59 | Interceptor( "MOD_INTERCEPTOR"); 60 | 61 | private final String preferenceKey; 62 | 63 | a(String prefKey) { 64 | this.preferenceKey = prefKey; 65 | } 66 | 67 | public String getPreferenceKey() { 68 | return preferenceKey; 69 | } 70 | 71 | } 72 | 73 | void a(b bVar); 74 | 75 | void a(tv.twitch.android.shared.ui.menus.s.b bVar, boolean z); 76 | } -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/k/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus.k; 2 | 3 | /** 4 | * Source: CheckableGroupModel 5 | */ 6 | public class b { 7 | } 8 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/l/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus.l; 2 | 3 | /** 4 | * Source: MenuModel 5 | */ 6 | public class b { 7 | } 8 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/m/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus.m; 2 | 3 | import android.view.View; 4 | import android.widget.ArrayAdapter; 5 | 6 | /** 7 | * Source: DropDownMenuModel 8 | */ 9 | public class a extends tv.twitch.android.shared.ui.menus.l.b { 10 | public interface a1 { // TODO: __RENAME_a 11 | void a(a aVar, int i2, boolean z); 12 | } 13 | 14 | public a(ArrayAdapter arrayAdapter, int i2, String str, String str2, String str3, View.OnClickListener onClickListener, a1 a1Var) {} 15 | } 16 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/o/a.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus.o; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.view.View; 5 | 6 | import kotlin.jvm.c.g; 7 | 8 | /** 9 | * Source: InfoMenuModel 10 | */ 11 | public class a extends tv.twitch.android.shared.ui.menus.l.b { 12 | public a(String str, String str2, String str3, Integer num, Integer num2, Drawable drawable, View.OnClickListener onClickListener) { 13 | } 14 | 15 | public a(String str, String str2, String str3, Integer num, Integer num2, Drawable drawable, View.OnClickListener onClickListener, int i2, g gVar) {} 16 | } 17 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/android/shared/ui/menus/s/b.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.android.shared.ui.menus.s; 2 | 3 | 4 | import android.graphics.drawable.Drawable; 5 | import android.view.View; 6 | 7 | import kotlin.jvm.c.g; 8 | import tv.twitch.android.shared.ui.menus.j; 9 | 10 | 11 | /** 12 | * Source: ToggleMenuModel 13 | */ 14 | public class b extends tv.twitch.android.shared.ui.menus.l.b { 15 | public b(String str, String str2, String str3, boolean z, boolean z2, Drawable drawable, boolean z3, String str4, boolean z4, String str5, Integer num, Integer num2, j.a aVar, View.OnClickListener onClickListener) {} 16 | 17 | public b(String str, String str2, String str3, boolean z, boolean z2, Drawable drawable, boolean z3, String str4, boolean z4, String str5, Integer num, Integer num2, j.a aVar, View.OnClickListener onClickListener, int i2, g gVar) {} 18 | 19 | public final j.a t() { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/chat/ChatEmoticon.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.chat; 2 | 3 | 4 | public class ChatEmoticon { 5 | public String emoticonId; 6 | public boolean isRegex; 7 | public String match; 8 | public String url = null; // TODO: __ADD_FIELD 9 | } 10 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/chat/ChatEmoticonSet.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.chat; 2 | 3 | public class ChatEmoticonSet { 4 | public String emoticonSetId; 5 | public ChatEmoticon[] emoticons; 6 | } 7 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/chat/ChatLiveMessage.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.chat; 2 | 3 | public class ChatLiveMessage { 4 | public String messageId; 5 | public ChatMessageInfo messageInfo; 6 | } 7 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/chat/ChatMessageInfo.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.chat; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | 6 | public class ChatMessageInfo { 7 | //public ChatMessageBadge[] badges; 8 | public String displayName; 9 | //public ChatMessageFlags flags = new ChatMessageFlags(); 10 | public HashMap messageTags = new HashMap<>(); 11 | public String messageType; 12 | public int nameColorARGB; 13 | public int numBitsSent; 14 | public int timestamp; 15 | //public ChatMessageToken[] tokens; 16 | public int userId; 17 | public ChatUserMode userMode = new ChatUserMode(); 18 | public String userName; 19 | 20 | 21 | public Date dateFromTimestamp() { 22 | return new Date(((long) this.timestamp) * 1000); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /twitch/src/main/java/tv/twitch/chat/ChatUserMode.java: -------------------------------------------------------------------------------- 1 | package tv.twitch.chat; 2 | 3 | public class ChatUserMode { 4 | public boolean administrator; 5 | public boolean banned; 6 | public boolean broadcaster; 7 | public boolean globalModerator; 8 | public boolean moderator; 9 | public boolean staff; 10 | public boolean subscriber; 11 | public boolean system; 12 | public boolean vip; 13 | } --------------------------------------------------------------------------------