├── .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 extends ChatLiveMessage> 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 extends t> 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 extends t> 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 extends ChatLiveMessage> list...)
293 | */
294 | public final static List extends ChatLiveMessage> hookLiveMessages(List extends ChatLiveMessage> 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