Compare commits

...
Author SHA1 Message Date
gdlbo 63a3a677fe Update preferences_other.xml 2024-03-21 21:39:28 +03:00
gdlbo 889f806fb9 Update UserApiParser.smali 2024-03-21 21:38:08 +03:00
gdlbo 3faa58ce68 idk 2024-03-21 21:33:12 +03:00
gdlbo 41f0bf4169 init 2024-03-21 21:01:09 +03:00
gdlbo af4f069eb4 fixed sony crash on android 14
https://t.me/vtosterschat/3499552/3563197
2024-03-21 08:41:42 +03:00
gdlbo 904814e5cb fixed batchmessages applying 2024-03-19 22:49:06 +03:00
gdlbo c240db520d fixed tabs text color in vkid 2024-03-19 22:33:34 +03:00
gdlbo 8f9235e6db added test feature of alter load msgs 2024-03-19 22:16:00 +03:00
gdlbo 10b27566c0 fix #1350 #1178 #1045
also fixed webview coloring after theme change
2024-03-19 21:43:59 +03:00
Artur Skubei 16b67cf6c6 Music Caching v2 (#1283)
* Playlist caching
* Playlist gzip support
* Many catalog fixes
2024-03-19 19:33:05 +03:00
gdlbo 23436f1026 some sponsorpost fixes 2024-03-19 19:29:29 +03:00
gdlbo 6998bdba1c added new block to remove
cyz unsupported
2024-03-18 10:38:33 +03:00
aliveoutside 5aa0fe9959 Merge remote-tracking branch 'sunmisc/main' #1554 2024-03-18 03:17:30 +03:00
gdlbo 88f0939f04 StrictMode corrections 2024-03-17 12:10:22 +03:00
Vologhat 73653fa2e0 optimize getting of global app context (#1583) 2024-03-16 02:24:51 +03:00
gdlbo c812b13446 fixed cold start 2024-03-14 08:52:01 +03:00
gdlbo ea9291a5eb fixed background menu in nonmilk 2024-03-13 12:55:32 +03:00
gdlbo 8d93e298b4 fixed default value of undelete prefix 2024-03-13 12:42:41 +03:00
gdlbo 49d448ad99 added some null checks 2024-03-13 12:30:27 +03:00
gdlbo 6bcc278dee fixed progress icon for downloaders 2024-03-12 15:30:17 +03:00
gdlbo cff0a98227 fixed download video notification 2024-03-12 15:14:01 +03:00
gdlbo 3529eb7ba1 moved metadataSeparator to MusicFragment 2024-03-12 15:08:25 +03:00
gdlbo 2ee4bf1107 new switch frag solution 2024-03-12 14:26:59 +03:00
gdlbo ac43a7794b fix #1260 2024-03-12 14:24:30 +03:00
gdlbo 47b2519ca5 fixed #1471 2024-03-12 13:25:16 +03:00
aliveoutside 701674ce45 rollback accidentally changed ndk version 2024-03-11 10:18:17 +03:00
aliveoutside e19a005362 Merge branch 'feat/java-mpeg-demux'
# Conflicts:
#	app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
#	smali/res/xml/preferences_media.xml
2024-03-11 10:10:37 +03:00
gdlbo e11e97cd29 added download notification for Video Downloader 2024-03-08 20:04:37 +03:00
gdlbo ab2715456f fixed freeze download video 2024-03-08 19:07:28 +03:00
gdlbo 68526f0ba2 added encryptor name in decrypt method 2024-03-08 14:37:35 +03:00
gdlbo b3243f1d41 removed compression in fb lib 2024-03-07 22:17:01 +03:00
gdlbo 175866ddc5 fixed crash and photo upload 2024-03-07 21:38:29 +03:00
gdlbo f691f8cc9e updated rounding dialog 2024-03-07 20:01:51 +03:00
gdlbo 305614dae6 fix #1581 2024-03-07 18:05:20 +03:00
gdlbo 4bbcde750a fixed execute.getAudioServicePackages 2024-03-07 16:50:38 +03:00
gdlbo e159b4f594 moved native to main inj 2024-03-04 19:31:46 +03:00
gdlbo 204b0c6e2c moved dockbar editor to main settings 2024-03-03 16:13:45 +03:00
gdlbo 522f6cd42b removed useless cache buttons 2024-03-03 09:53:02 +03:00
gdlbo 7dd9e5ff3a more gzip support 2024-03-02 23:33:18 +03:00
Artur Skubei 07f8542f56 SponsorPost vote system (#1562)
also added gzip support for all methods
2024-03-02 22:24:15 +03:00
Artur Skubei 6b1e6f07c5 Internal VK Proxy Support (#1579)
thanks @awers24 for tests
2024-03-02 21:47:34 +03:00
gdlbo 5466dd72dd some lastfm fixes 2024-03-01 22:50:31 +03:00
Artur Skubei 5ac3b9cf6e Another ndk fix (#1572) 2024-02-25 22:07:42 +03:00
Artur Skubei de6edc795a Update build.yml 2024-02-25 21:46:27 +03:00
Artur Skubei 19d36272cb ndk fix (#1571) 2024-02-25 21:43:08 +03:00
gdlbo d2fe435ad2 added music fragment
migration from media content
2024-02-25 19:47:23 +03:00
aliveoutside 1a7a0e7f4f recover accidentally deleted vk ffmpeg libs 2024-02-17 15:02:05 +03:00
aliveoutside 953aa790e3 fix typo in logging 2024-02-16 23:51:14 +03:00
aliveoutside c1de68893c implement id3v2.3 tagging 2024-02-16 21:23:57 +03:00
aliveoutside e0d3f22f16 improve demuxer performance
use buffered io for input and output streams
reuse single bytebuffer instead of creating it for every packet
2024-02-16 06:32:53 +03:00
aliveoutside 60e32df046 use java demuxer to extract mp3 from .ts files instead of ffmpeg 2024-02-16 01:13:39 +03:00
sunmisc bbc4aa98f7 exception is passed to onFailure 2024-02-15 18:49:14 +03:00
sunmisc 287af14589 transfer audio download to ffmpeg 2024-02-15 16:36:29 +03:00
sunmisc 404594df87 unnecessary atomic 2024-02-10 15:11:30 +03:00
gdlbo 3b8af6e477 Merge branch 'main' into main 2024-02-07 13:57:33 +03:00
aliveoutside cb38660a3a code reformat 2024-02-05 21:37:18 +03:00
aliveoutside f6f0fb3d08 fix onSuccess not being called on finish 2024-02-05 21:15:10 +03:00
sunmisc dab50535ef optimize 2024-02-05 18:28:28 +03:00
sunmisc d9300fc621 may have fixed the race on CIPHER.init 2024-02-05 18:07:23 +03:00
sunmisc 48eba3ccbb the track is already being downloaded in another thread 2024-02-05 16:07:24 +03:00
sunmisc 2336da53be immediately concatenate bytes without creating files 2024-02-05 15:22:45 +03:00
317 changed files with 21173 additions and 9228 deletions
+12 -3
View File
@@ -21,14 +21,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up NDK
uses: nttld/setup-ndk@v1
with:
ndk-version: r25c
add-to-path: false
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- uses: actions/checkout@v3
- name: Download tools
run: |
sudo apt update
@@ -47,6 +54,8 @@ jobs:
java -jar scripts/apktool.jar b smali -o smali/VTLite.apk --use-aapt2 -p scripts/framework/
zipalign -p -f 4 smali/VTLite.apk smali/VTLite.apk_aligned
mv smali/VTLite.apk_aligned smali/VTLite.apk
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Sign and verify the apk
run: |
+3 -1
View File
@@ -78,7 +78,7 @@ android {
targetCompatibility JavaVersion.VERSION_14
}
namespace 'ru.vtosters.lite'
ndkVersion '26.1.10909125'
ndkVersion '25.2.9519653'
buildFeatures {
aidl true
}
@@ -97,6 +97,8 @@ dependencies {
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
implementation 'com.google.guava:guava:32.0.0-android'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation fileTree(dir: 'libs', include: ['*.aar'])
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
}
Binary file not shown.
+1 -1
View File
@@ -35,7 +35,7 @@
-optimizationpasses 5
-keepattributes SourceFile, LineNumberTable
-keep,allowoptimization class ru.vtosters.hooks.**, ru.vtosters.lite.utils.AndroidUtils, ru.vtosters.lite.utils.NetworkUtils, ru.vtosters.lite.utils.CrashReporter, ru.vtosters.lite.ui.activities.**, ru.vtosters.lite.services.**, ru.vtosters.lite.ui.fragments.**, ru.vtosters.lite.themes.**, ru.vtosters.lite.music.hook.**, ru.vtosters.lite.music.cache.injectors.**, ru.vtosters.lite.ui.wallpapers.NativeEffects, com.aefyr.tsg.g2.TelegramStickersService {
-keep,allowoptimization class ru.vtosters.hooks.**, ru.vtosters.sponsorpost.internal.Native, ru.vtosters.lite.utils.AndroidUtils, ru.vtosters.lite.utils.NetworkUtils, ru.vtosters.lite.utils.CrashReporter, ru.vtosters.lite.ui.activities.**, ru.vtosters.lite.services.**, ru.vtosters.lite.ui.fragments.**, ru.vtosters.lite.themes.**, ru.vtosters.lite.music.hook.**, ru.vtosters.lite.music.cache.injectors.**, ru.vtosters.lite.ui.wallpapers.NativeEffects, com.aefyr.tsg.g2.TelegramStickersService {
public <fields>;
public <methods>;
public <init>(...);
@@ -1,124 +0,0 @@
package com.arthenica.ffmpegkit;
import android.os.Build;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import java.util.Collections;
import java.util.List;
public class NativeLoader {
static final String[] FFMPEG_LIBRARIES = {"avutil", "swresample", "avcodec", "avformat", "avfilter", "avdevice"};
private static void loadLibrary(String libraryName) {
System.loadLibrary(libraryName);
}
private static List<String> loadExternalLibraries() {
//return Packages.getExternalLibraries();
return Collections.emptyList();
}
private static String loadNativeAbi() {
return AbiDetect.getNativeAbi();
}
static String loadAbi() {
return AbiDetect.getAbi();
}
static String loadPackageName() {
return Packages.getPackageName();
}
static String loadVersion() {
return FFmpegKitConfig.getVersion();
}
static boolean loadIsLTSBuild() {
return AbiDetect.isNativeLTSBuild();
}
static int loadLogLevel() {
return FFmpegKitConfig.getNativeLogLevel();
}
static String loadBuildDate() {
return FFmpegKitConfig.getBuildDate();
}
static void enableRedirection() {
FFmpegKitConfig.enableRedirection();
}
static void loadFFmpegKitAbiDetect() {
loadLibrary("ffmpegkit_abidetect");
}
static boolean loadFFmpeg() {
boolean nativeFFmpegLoaded = false;
boolean nativeFFmpegTriedAndFailed = false;
if (AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
try {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary + "_neon");
}
nativeFFmpegLoaded = true;
} catch (final Error e) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
nativeFFmpegTriedAndFailed = true;
}
}
if (!nativeFFmpegLoaded) {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary);
}
}
return nativeFFmpegTriedAndFailed;
}
static void loadFFmpegKit(boolean nativeFFmpegTriedAndFailed) {
boolean nativeFFmpegKitLoaded = false;
if (!nativeFFmpegTriedAndFailed && "arm-v7a".equals(loadNativeAbi())) {
try {
loadLibrary("ffmpegkit_armv7a_neon");
nativeFFmpegKitLoaded = true;
AbiDetect.setArmV7aNeonLoaded();
} catch (Error e) {
Log.i("ffmpeg-kit", String.format("NEON supported armeabi-v7a ffmpegkit library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
}
}
if (!nativeFFmpegKitLoaded) {
loadLibrary("ffmpegkit");
}
}
static String getDeviceDebugInformation() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("brand: ");
stringBuilder.append(Build.BRAND);
stringBuilder.append(", model: ");
stringBuilder.append(Build.MODEL);
stringBuilder.append(", device: ");
stringBuilder.append(Build.DEVICE);
stringBuilder.append(", api level: ");
stringBuilder.append(Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 21) {
stringBuilder.append(", abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_ABIS));
stringBuilder.append(", 32bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_32_BIT_ABIS));
stringBuilder.append(", 64bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_64_BIT_ABIS));
} else {
stringBuilder.append(", cpu abis: ");
stringBuilder.append(Build.CPU_ABI);
stringBuilder.append(", cpu abi2s: ");
stringBuilder.append(Build.CPU_ABI2);
}
return stringBuilder.toString();
}
}
@@ -1,732 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>Abstract class that includes common variables and methods for different <code>Exceptions</code> implementations.
*
* @author Taner Sener
* @since 0.1.0
*/
public abstract class AbstractExceptions {
/**
* <p>Default max depth used in methods which do not have a maxDepth argument.
*/
public static final int DEFAULT_MAX_DEPTH = 10;
/**
* <p>Default value for ignoring all causes when stack trace elements are printed or converted to string.
*/
public static final boolean DEFAULT_IGNORE_ALL_CAUSES = false;
/**
* <p>Stores global root package names.
*/
public static final Set<String> rootPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global group package names.
*/
public static final Set<String> groupPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore package names.
*/
public static final Set<String> ignorePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore cause package names.
*/
public static final Set<String> ignoreCausePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores the value of global ignore all causes option.
*/
public static boolean ignoreAllCauses = DEFAULT_IGNORE_ALL_CAUSES;
/**
* <p>Stores the global stack trace serializer implementation.
*/
public static StackTraceElementSerializer stackTraceElementSerializer;
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
rootPackageSet.add(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
rootPackageSet.clear();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
groupPackageSet.add(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
groupPackageSet.clear();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return stackTraceElementSerializer;
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.stackTraceElementSerializer = stackTraceElementSerializer;
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
ignorePackageSet.add(packageString);
if (ignoreCauseClasses) {
ignoreCausePackageSet.add(packageString);
}
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
ignorePackageSet.clear();
ignoreCausePackageSet.clear();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return ignoreAllCauses;
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.ignoreAllCauses = ignoreAllCauses;
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), new HashSet<String>(), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), Collections.singleton(groupPackage), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using parameters provided.
*
* @param throwable parent throwable
* @param isCause throwable is a cause or not
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean isCause, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final int maxDepth, final boolean ignoreAllCauses) {
final StringBuilder builder = new StringBuilder();
if (throwable == null) {
return "";
}
final String className = throwable.getClass().getName();
final StackTraceElement[] stackTraceElements;
if (maxDepth > 0) {
stackTraceElements = getStackTrace(throwable, maxDepth);
} else {
stackTraceElements = getStackTrace(throwable, rootPackageSet, ignorePackageSet);
}
String message = throwable.getLocalizedMessage();
if (isEmpty(message)) {
message = throwable.getMessage();
}
/* PRINT MESSAGE */
if (isCause) {
builder.append(System.lineSeparator());
builder.append("Caused by: ");
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
} else {
builder.append(System.lineSeparator());
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
}
/* PRINT SHORT STACK TRACE */
String currentGroupPackage = null;
StackTraceElement firstStackTraceElementInTheGroup = null;
int currentGroupCount = 0;
for (StackTraceElement traceElement : stackTraceElements) {
String traceElementClassName = traceElement.getClassName();
String groupPackageMatch = getContainingPackage(traceElementClassName, groupPackageSet);
if (groupPackageMatch != null) {
if (!groupPackageMatch.equals(currentGroupPackage)) {
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
currentGroupPackage = groupPackageMatch;
firstStackTraceElementInTheGroup = traceElement;
currentGroupCount = 1;
} else {
currentGroupCount++;
}
} else {
currentGroupCount = appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
builder.append(stackTraceElementSerializer.toString(traceElement));
}
currentGroupPackage = null;
}
}
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
final Throwable cause = throwable.getCause();
if (cause != null && !containsPackage(className, ignoreCausePackageSet) && !ignoreAllCauses) {
builder.append(getStackTraceString(cause, true, rootPackageSet, groupPackageSet, ignorePackageSet, maxDepth, ignoreAllCauses));
}
return builder.toString();
}
/**
* <p>Appends stack trace group information for the given parameters.
*
* @param stringBuilder string builder that group stack trace information will be appended
* @param currentGroupPackage package name of the current group
* @param numberOfElementsInTheCurrentGroup number of elements in the current group
* @param firstStackTraceElementInTheGroup first stack trace element of this group
* @return new value for the group element count
*/
public static int appendStackTraceGroupElement(final StringBuilder stringBuilder, final String currentGroupPackage, final int numberOfElementsInTheCurrentGroup, final StackTraceElement firstStackTraceElementInTheGroup) {
if (numberOfElementsInTheCurrentGroup > 0) {
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
stringBuilder.append((numberOfElementsInTheCurrentGroup == 1) ? stackTraceElementSerializer.toString(firstStackTraceElementInTheGroup) : String.format("%s%s ... %d more", stackTraceElementSerializer.getModuleName(firstStackTraceElementInTheGroup), currentGroupPackage, (numberOfElementsInTheCurrentGroup - 1)));
}
}
return 0;
}
/**
* <p>Returns true if package of the given class name matches one of package names provided in
* <code>packageSet</code>.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return true if package of the given class name matches one of package names provided in <code>packageSet</code>,
* false otherwise
*/
public static boolean containsPackage(final String fullClassName, final Set<String> packageSet) {
return getContainingPackage(fullClassName, packageSet) != null;
}
/**
* <p>Checks if one of the package names in the <code>packageSet</code> matches the package of the given class name
* and returns the matching package name.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return one of the package names in the <code>packageSet</code> that matches the package of the given class name
* or null if no matching package name is found in the <code>packageSet</code>
*/
public static String getContainingPackage(final String fullClassName, final Set<String> packageSet) {
for (final String parentExceptionPackage : packageSet) {
if (fullClassName.startsWith(parentExceptionPackage)) {
return parentExceptionPackage;
}
}
return null;
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
final StringBuilder messageBuilder = new StringBuilder();
getAllMessages(throwable, messageBuilder);
return messageBuilder.toString();
}
/**
* <p>Adds all messages found in the exception chain of the <code>throwable</code> into the given message builder.
*
* @param throwable parent throwable
* @param messageBuilder message builder
*/
public static void getAllMessages(final Throwable throwable, final StringBuilder messageBuilder) {
if (throwable != null) {
final String message = throwable.getMessage();
if (!isEmpty(message)) {
if (messageBuilder.length() != 0) {
messageBuilder.append(System.lineSeparator());
messageBuilder.append(" - Caused by: ");
}
messageBuilder.append(message);
}
getAllMessages(throwable.getCause(), messageBuilder);
}
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using elements found until the maxDepth and
* elements of it.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be returned
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final int maxDepth) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
if (throwable != null) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (int i = 0; (i < stackTrace.length) && (i < maxDepth); i++) {
list.add(stackTrace[i]);
}
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using packages provided and returns elements
* of it.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> ignorePackageSet) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
final ArrayList<StackTraceElement> partialList = new ArrayList<StackTraceElement>();
if (throwable != null) {
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
final String className = stackTraceElement.getClassName();
if (!isEmpty(className)) {
if (containsPackage(className, rootPackageSet)) {
list.addAll(partialList);
list.add(stackTraceElement);
} else if (!containsPackage(className, ignorePackageSet)) {
partialList.add(stackTraceElement);
}
}
}
}
if (list.isEmpty()) {
list.addAll(partialList);
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return containsCause(throwable, causeClass, null);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return (searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH) != null);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return getCause(throwable, DEFAULT_MAX_DEPTH);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
if (throwable == null) {
return null;
}
if (maxDepth <= 0) {
return throwable;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return throwable;
} else {
return getCause(cause, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return searchCause(throwable, causeClass, null, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
if (throwable == null) {
return null;
}
if (isEmpty(causeMessage)) {
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
} else {
if (throwable.getClass().equals(causeClass) && getAllMessages(throwable).toLowerCase().contains(causeMessage.toLowerCase())) {
return throwable;
}
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, causeMessage, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
if (throwable == null) {
return null;
}
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, maxDepth - 1);
}
}
/**
* <p>Checks if given string is empty or not.
*
* @param value string to check
* @return true if given string is null or has zero length after trim(), false otherwise
*/
public static boolean isEmpty(final String value) {
if (value == null) {
return true;
}
return value.trim().length() == 0;
}
/**
* <p>Returns the package name of this class.
*
* @param className fully qualified class name
* @return name of the package
*/
public static String packageName(final String className) {
if (className == null) {
return "";
}
int index = className.lastIndexOf(".");
if (index >= 0) {
return className.substring(0, index);
} else {
return "";
}
}
}
@@ -1,74 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
/**
* <p>Base interface for classes which can serialize {@link StackTraceElement} objects.
*
* @author Taner Sener
* @since 0.1.0
*/
public interface StackTraceElementSerializer {
/**
* <p>Converts the given <code>stackTraceElement</code> into a string.
*
* @param stackTraceElement stack trace element to convert
* @return string representing the given <code>stackTraceElement</code>
*/
String toString(final StackTraceElement stackTraceElement);
/**
* <p>Returns module name for the given <code>stackTraceElement</code>.
*
* @param stackTraceElement stack trace element to use
* @return module name of the given <code>stackTraceElement</code> or an empty string if implementation does not
* support printing module names
*/
String getModuleName(final StackTraceElement stackTraceElement);
/**
* <p>Returns string definition used to print native methods.
*
* @return string definition used to print native methods
*/
String getNativeMethodDefinition();
/**
* <p>Returns string definition used to print methods with unknown source.
*
* @return string definition used to print methods with unknown source
*/
String getUnknownSourceDefinition();
}
@@ -1,423 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception.java;
import com.arthenica.smartexception.AbstractExceptions;
import com.arthenica.smartexception.StackTraceElementSerializer;
import java.util.Set;
/**
* <p>Utility class to handle Throwable objects and stack traces. This implementation is compatible with all Java and
* Android versions.
*
* @author Taner Sener
* @since 0.1.0
*/
public class Exceptions {
static {
AbstractExceptions.setStackTraceElementSerializer(new StackTraceElementSerializer() {
@Override
public String toString(StackTraceElement stackTraceElement) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(stackTraceElement.getClassName());
stringBuilder.append(".");
stringBuilder.append(stackTraceElement.getMethodName());
if (stackTraceElement.isNativeMethod()) {
stringBuilder.append("(Native Method)");
} else if ((stackTraceElement.getFileName() != null) && (stackTraceElement.getFileName().length() > 0)) {
stringBuilder.append("(");
stringBuilder.append(stackTraceElement.getFileName());
if (stackTraceElement.getLineNumber() >= 0) {
stringBuilder.append(":");
stringBuilder.append(stackTraceElement.getLineNumber());
}
stringBuilder.append(")");
} else {
stringBuilder.append("(Unknown Source)");
}
return stringBuilder.toString();
}
@Override
public String getModuleName(StackTraceElement stackTraceElement) {
return "";
}
@Override
public String getNativeMethodDefinition() {
return "(Native Method)";
}
@Override
public String getUnknownSourceDefinition() {
return "(Unknown Source)";
}
});
}
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
AbstractExceptions.registerRootPackage(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
AbstractExceptions.clearRootPackages();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
AbstractExceptions.registerGroupPackage(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
AbstractExceptions.clearGroupPackages();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return AbstractExceptions.getStackTraceElementSerializer();
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.setStackTraceElementSerializer(stackTraceElementSerializer);
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
AbstractExceptions.registerIgnorePackage(packageString, ignoreCauseClasses);
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
AbstractExceptions.clearIgnorePackages();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return AbstractExceptions.getIgnoreAllCauses();
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.setIgnoreAllCauses(ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return AbstractExceptions.getStackTraceString(throwable);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage, groupPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth, ignoreAllCauses);
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
return AbstractExceptions.getAllMessages(throwable);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.containsCause(throwable, causeClass);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.containsCause(throwable, causeClass, causeMessage);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return AbstractExceptions.getCause(throwable);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getCause(throwable, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.searchCause(throwable, causeClass);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, maxDepth);
}
}
@@ -24,8 +24,8 @@ public class MediaImageEncoder {
return compressBitmap(bitmap, file, CompressFormat.JPEG, quality);
}
private static boolean compressBitmapLossless(Bitmap bitmap, File file) {
return compressBitmap(bitmap, file, CompressFormat.PNG, 100);
public static boolean needToCompress() {
return Preferences.getPreferences().getBoolean("compressPhotos", true);
}
private static boolean compressBitmapNative(Bitmap bitmap, File file, int quality) {
@@ -37,7 +37,7 @@ public class MediaImageEncoder {
byte[] jpegData = MediaNative.compressBitmapJpeg(bitmap, quality);
if (jpegData == null) {
return false;
return compressBitmapJpeg(bitmap, file, quality);
}
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
@@ -51,43 +51,21 @@ public class MediaImageEncoder {
public static boolean encodePicture(Bitmap bitmap, File file, int quality) {
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
switch (Preferences.getPreferences().getString("compression", "jpeg")) {
case "skip" -> {
return compressBitmapLossless(bitmap, file);
}
case "jpeg" -> {
return compressBitmapJpeg(bitmap, file, quality);
}
case "native" -> {
return compressBitmapNative(bitmap, file, quality);
}
}
}
return false;
}
public static boolean needToSkipCompression() {
return Preferences.getPreferences().getString("compression", "jpeg").equals("skip");
}
public static boolean needToCompress() {
return !needToSkipCompression();
}
public static boolean encodeJpeg(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 90);
}
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 95);
}
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
return needToCompress() ? compressBitmapNative(bitmap, file, quality) : compressBitmapJpeg(bitmap, file, quality);
return compressBitmapNative(bitmap, file, quality);
} else {
return false;
}
}
}
public static boolean encodeJpeg(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 100);
}
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
}
@@ -248,7 +248,6 @@ public final class MediaNative {
if (isMediaLoaded) {
return;
}
NativeLibLoader.loadLibrary("ffmpeg");
isMediaLoaded = NativeLibLoader.loadLibrary("vkmedia");
isMediaLoaded = NativeLibLoader.loadLibrary("vkmediaencoder");
}
@@ -0,0 +1,106 @@
package com.vk.newsfeed.holders;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.dto.newsfeed.entries.NewsEntry;
import com.vk.dto.newsfeed.entries.Post;
import com.vk.dto.newsfeed.entries.PromoPost;
import com.vk.extensions.ViewExtKt;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.newsfeed.NewsFeedFiltersUtils;
import ru.vtosters.sponsorpost.internal.Native;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.internal.VotesService;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
public final class AdMarkHolder extends BaseNewsEntryHolder<NewsEntry> {
private final TextView F;
@SuppressLint("ResourceType")
public AdMarkHolder(ViewGroup viewGroup) {
super(R.layout.marked_as_ads, viewGroup);
View view = ((RecyclerView.ViewHolder) this).itemView;
this.F = ViewExtKt.a(view, R.id.text, (View.OnClickListener) null);
}
@SuppressLint("ResourceType")
public void b(NewsEntry newsEntry) { // onBind
String disclamer = null;
Drawable drawable = null;
int height = 0;
if (newsEntry instanceof Post) {
Post post = ((Post) newsEntry);
if (post.L1()) {
drawable = VKThemeHelper.a(R.drawable.marked_as_ads, R.attr.icon_tertiary);
int ownerId = post.b();
int postId = post.P1();
int date = post.K();
String text = post.getText();
if (PostsPreferences.isPostAd(ownerId, postId)) {
disclamer = "SponsorPost: Реклама";
if (Preferences.isValidSignature() && Native.canVote()) {
this.F.setOnClickListener(listener -> voteDialog(ownerId, postId, date, this.F.getContext()));
}
} else if (VotesPreferences.isPostAd(ownerId, postId)) {
disclamer = "SponsorPost: Возможно реклама";
if (Preferences.isValidSignature() && Native.canVote()) {
this.F.setOnClickListener(listener -> voteDialog(ownerId, postId, date, this.F.getContext()));
}
} else if (NewsFeedFiltersUtils.sponsorFilters(text)) {
disclamer = "SponsorPost: Заблокировано фильтрами";
this.F.setOnClickListener(listener -> AndroidUtils.sendToast("Заблокировано по выражению: " + NewsFeedFiltersUtils.sponsorFiltersBanWord(text)));
} else {
disclamer = m(R.string.sponsored_post_in_group);
}
height = e0().getDimensionPixelSize(R.dimen.newsfeed_ad_mark_post_height);
}
} else if (newsEntry instanceof PromoPost) {
PromoPost promoPost = (PromoPost) newsEntry;
if (!promoPost.E1().isEmpty()) {
height = e0().getDimensionPixelSize(R.dimen.newsfeed_ad_mark_promo_post_height);
disclamer = promoPost.E1();
}
}
this.F.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, null, null, null);
this.F.setMinHeight(height);
this.F.setText(disclamer);
}
public static void voteDialog(int ownerId, int postId, int date, Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle("SponsorPost")
.setMessage("Этот пост является рекламным?")
.setPositiveButton("Да", ((dialog, which) -> {
int resp = VotesService.ratePost(ownerId, postId, date, true).optInt("code");
if (resp == 201 || resp == 100) {
AndroidUtils.sendToast("Спасибо за голос!");
} else {
AndroidUtils.sendToast("Ваш голос уже учтен");
}
}))
.setNeutralButton("Нет", ((dialog, which) -> {
int resp = VotesService.ratePost(ownerId, postId, date, false).optInt("code");
if (resp == 201 || resp == 100) {
AndroidUtils.sendToast("Спасибо за голос!");
} else {
AndroidUtils.sendToast("Ваш голос уже учтен");
}
}))
.show();
}
}
+33 -32
View File
@@ -19661,38 +19661,39 @@ public final class R {
public static final int cache_size = 0x7f12188d; // 2131892365
public static final int cache_size_summ = 0x7f12188e; // 2131892366
public static final int clear_all_cache = 0x7f12188f; // 2131892367
public static final int invalidate_theme_cache_each_update_title = 0x7f121890; // 2131892368
public static final int invertFilters = 0x7f121891; // 2131892369
public static final int invertFilters_summ = 0x7f121892; // 2131892370
public static final int invertcached_summ = 0x7f121893; // 2131892371
public static final int invertcached_title = 0x7f121894; // 2131892372
public static final int microg_dialog1 = 0x7f121895; // 2131892373
public static final int microg_dialog2 = 0x7f121896; // 2131892374
public static final int microg_dialog3 = 0x7f121897; // 2131892375
public static final int serverFeaturesDisable = 0x7f121898; // 2131892376
public static final int serverFeaturesDisable_summ = 0x7f121899; // 2131892377
public static final int sett_autoupdates = 0x7f12189a; // 2131892378
public static final int sett_autoupdates_summ = 0x7f12189b; // 2131892379
public static final int sett_battery = 0x7f12189c; // 2131892380
public static final int sett_battery_summ1 = 0x7f12189d; // 2131892381
public static final int sett_battery_summ2 = 0x7f12189e; // 2131892382
public static final int sett_chats = 0x7f12189f; // 2131892383
public static final int sett_chats_summ = 0x7f1218a0; // 2131892384
public static final int sett_community = 0x7f1218a1; // 2131892385
public static final int sett_community_summ = 0x7f1218a2; // 2131892386
public static final int sett_missing_links = 0x7f1218a3; // 2131892387
public static final int sett_missing_links_summ = 0x7f1218a4; // 2131892388
public static final int sett_nogms_summ = 0x7f1218a5; // 2131892389
public static final int sett_notifs = 0x7f1218a6; // 2131892390
public static final int sett_notifs_summ = 0x7f1218a7; // 2131892391
public static final int sett_recommendations = 0x7f1218a8; // 2131892392
public static final int tgs_token_missing = 0x7f1218aa; // 2131892394
public static final int tgs_token_reset = 0x7f1218ab; // 2131892395
public static final int tgs_token_reset_dialog = 0x7f1218ac; // 2131892396
public static final int tgs_token_reset_menu = 0x7f1218ad; // 2131892397
public static final int update_err = 0x7f1218ae; // 2131892398
public static final int update_latest = 0x7f1218af; // 2131892399
public static final int APKTOOL_DUMMY_18a9 = 0x7f1218a9; // 2131892393
public static final int disable_roaming_state = 0x7f121890; // 2131892368
public static final int disable_roaming_state_submit = 0x7f121891; // 2131892369
public static final int invalidate_theme_cache_each_update_title = 0x7f121892; // 2131892370
public static final int invertFilters = 0x7f121893; // 2131892371
public static final int invertFilters_summ = 0x7f121894; // 2131892372
public static final int invertcached_summ = 0x7f121895; // 2131892373
public static final int invertcached_title = 0x7f121896; // 2131892374
public static final int microg_dialog1 = 0x7f121897; // 2131892375
public static final int microg_dialog2 = 0x7f121898; // 2131892376
public static final int microg_dialog3 = 0x7f121899; // 2131892377
public static final int serverFeaturesDisable = 0x7f12189a; // 2131892378
public static final int serverFeaturesDisable_summ = 0x7f12189b; // 2131892379
public static final int sett_autoupdates = 0x7f12189c; // 2131892380
public static final int sett_autoupdates_summ = 0x7f12189d; // 2131892381
public static final int sett_battery = 0x7f12189e; // 2131892382
public static final int sett_battery_summ1 = 0x7f12189f; // 2131892383
public static final int sett_battery_summ2 = 0x7f1218a0; // 2131892384
public static final int sett_chats = 0x7f1218a1; // 2131892385
public static final int sett_chats_summ = 0x7f1218a2; // 2131892386
public static final int sett_community = 0x7f1218a3; // 2131892387
public static final int sett_community_summ = 0x7f1218a4; // 2131892388
public static final int sett_missing_links = 0x7f1218a5; // 2131892389
public static final int sett_missing_links_summ = 0x7f1218a6; // 2131892390
public static final int sett_nogms_summ = 0x7f1218a7; // 2131892391
public static final int sett_notifs = 0x7f1218a8; // 2131892392
public static final int sett_notifs_summ = 0x7f1218a9; // 2131892393
public static final int sett_recommendations = 0x7f1218aa; // 2131892394
public static final int tgs_token_missing = 0x7f1218ab; // 2131892395
public static final int tgs_token_reset = 0x7f1218ac; // 2131892396
public static final int tgs_token_reset_dialog = 0x7f1218ad; // 2131892397
public static final int tgs_token_reset_menu = 0x7f1218ae; // 2131892398
public static final int update_err = 0x7f1218af; // 2131892399
public static final int update_latest = 0x7f1218b0; // 2131892400
}
public static final class style {
@@ -12,12 +12,12 @@ import com.vk.im.engine.models.messages.NestedMsg;
import com.vk.libsqliteext.CustomSqliteExtensionsKt;
import io.requery.android.database.sqlite.SQLiteDatabase;
import io.requery.android.database.sqlite.SQLiteOpenHelper;
import ru.vtosters.hooks.other.Preferences;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.getString;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class DeletedMessagesHook {
@@ -69,7 +69,7 @@ public class DeletedMessagesHook {
}
private static String getPrefixUndelete() {
var prefs = getString("undeletemsg_prefix_value");
var prefs = Preferences.getPreferences().getString("undeletemsg_prefix_value", "🗑");
if (prefs.isEmpty()) {
return "\uD83D\uDDD1 ";
@@ -78,7 +78,7 @@ public class DeletedMessagesHook {
return prefs + " ";
}
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) throws NoSuchFieldException, IllegalAccessException {
public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) {
var cursor = getMessageFromDatabaseById(msgDeleteLpTask.d);
if (cursor == null) return;
@@ -157,4 +157,4 @@ public class DeletedMessagesHook {
return msgIdsList;
}
}
}
}
@@ -72,8 +72,9 @@ public class EncryptionMessagesHook {
public static String decryptMessage(String msgBody, int peer) {
try {
for (IMProcessor processor : EncryptProvider.processors) {
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null))
return "\uD83D\uDD12 " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null)) {
return "\uD83D\uDD12 " + processor.getUIName() + ": " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer)));
}
}
} catch (Exception e) {
e.printStackTrace();
@@ -0,0 +1,38 @@
package ru.vtosters.hooks;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
public class FirebaseGzipSupport {
public static JSONObject getJsonObjectFromConnection(URLConnection connection) throws IOException, JSONException {
InputStream inputStream = getDecompressedInputStream(connection);
String jsonString = readInputStream(inputStream);
return new JSONObject(jsonString);
}
private static InputStream getDecompressedInputStream(URLConnection connection) throws IOException {
String contentEncoding = connection.getHeaderField("Content-Encoding");
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
return new GZIPInputStream(connection.getInputStream());
}
return connection.getInputStream();
}
private static String readInputStream(InputStream inputStream) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
int read;
while ((read = bufferedReader.read()) != -1) {
sb.append((char) read);
}
return sb.toString();
}
}
@@ -0,0 +1,32 @@
package ru.vtosters.hooks;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.vk.dto.gift.GiftCategory;
import com.vtosters.lite.TabletDialogActivity;
import com.vtosters.lite.fragments.gifts.GiftCategoryFragment;
import ru.vtosters.lite.ui.fragments.SuperAppEditorFragment;
import ru.vtosters.lite.utils.NavigatorUtils;
import java.util.ArrayList;
public class GiftsFix {
public static Bundle staticBundle;
public static void openGiftCategoryFragment(Activity activity, ArrayList<Integer> userIds, GiftCategory giftCategory, int balance, String reference) {
Bundle bundle = new Bundle();
bundle.putIntegerArrayList("uids", userIds);
bundle.putParcelable("data", giftCategory);
bundle.putInt("balance", balance);
bundle.putString("ref", reference);
staticBundle = bundle;
NavigatorUtils.switchFragment(activity, GiftCategoryFragment.class);
}
public static Bundle getBundle() {
return staticBundle;
}
}
@@ -6,6 +6,7 @@ import android.os.Build;
import b.h.g.k.VKProgressDialog;
import com.aefyr.tsg.g2.TelegramStickersService;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.medianative.NativeLibLoader;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
@@ -17,10 +18,8 @@ import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.NavigatorUtils;
import ru.vtosters.lite.utils.VTVerifications;
import ru.vtosters.lite.utils.*;
import ru.vtosters.sponsorpost.internal.Native;
import ru.vtosters.sponsorpost.utils.Updates;
import static ru.vtosters.hooks.other.Preferences.checkupdates;
@@ -67,6 +66,10 @@ public class MainActivityInjector {
// VKIDProtection.alert(activity);
//needs to show selected tgs pack count in settings after cold launch
TelegramStickersService.getInstance(activity);
if (AccountManagerUtils.isLogin() && !Preferences.serverFeaturesDisable() && Preferences.isValidSignature()) {
Native.canVote = NativeLibLoader.loadLibrary("sponsorpost");
}
}
private static void updateBinsAndTmpArchive(Activity activity) {
@@ -80,7 +83,7 @@ public class MainActivityInjector {
ThemesManager.generateModApk(ThemesUtils.getReservedAccent());
activity.runOnUiThread(LifecycleUtils::restartApplication);
} catch (Throwable e) {
e.printStackTrace();
e.fillInStackTrace();
activity.runOnUiThread(() -> {
dialog.cancel();
new VkAlertDialog.Builder(activity)
@@ -11,13 +11,12 @@ import com.vk.discover.DiscoverItemDecorator;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.internal.VotesService;
import ru.vtosters.sponsorpost.services.PostService;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.*;
import static java.lang.Long.MAX_VALUE;
import static ru.vtosters.hooks.other.Preferences.*;
@@ -102,8 +101,16 @@ public class NewsfeedHook {
public static void takeOwnerIdSponsorPost(int ownerid) {
if (NetworkUtils.isNetworkConnected() && PostsPreferences.isEnabled() && !Preferences.serverFeaturesDisable()) {
VTExecutors.getIoScheduler().a(() -> {
List<Long> postIds = PostService.getPostIdsByOwnerId((long) ownerid, 0L);
PostsPreferences.saveGroupSpecifiedPosts(postIds, (long) ownerid);
if (PostsPreferences.isGroupAd(ownerid) || VotesPreferences.isGroupAd(ownerid)) {
if (VotesPreferences.isEnabled()) {
Map<String, List<String>> postIds = VotesService.getPostIdsByOwnerId((long) ownerid, 0L);
VotesPreferences.saveGroupSpecifiedPosts(postIds.get("vote"), (long) ownerid);
PostsPreferences.saveGroupSpecifiedPosts(postIds.get("prod"), (long) ownerid);
} else {
List<Long> postIds = PostService.getPostIdsByOwnerId((long) ownerid, 0L);
PostsPreferences.saveGroupSpecifiedPosts(postIds, (long) ownerid);
}
}
});
}
}
@@ -112,17 +119,28 @@ public class NewsfeedHook {
long ownerid = Long.parseLong(strArr[0].split("_")[0]);
long postId = Long.parseLong(strArr[0].split("_")[1]);
if (PostsPreferences.isPostAd(ownerid, postId)) {
if (PostsPreferences.isPostAd(ownerid, postId) || VotesPreferences.isPostAd(ownerid, postId)) {
return;
}
if (NetworkUtils.isNetworkConnected() && PostsPreferences.isEnabled() && !Preferences.serverFeaturesDisable() && PostsPreferences.isGroupAd(ownerid)) {
if (NetworkUtils.isNetworkConnected() && PostsPreferences.isEnabled() && !Preferences.serverFeaturesDisable()) {
VTExecutors.getIoScheduler().a(() -> {
boolean postIds = PostService.isPostAd(ownerid, postId);
if (VotesPreferences.isEnabled()) {
boolean postIds = VotesService.isPostAd(ownerid, postId);
if (postIds) {
PostsPreferences.saveAdPostInfo(ownerid, postId);
sendToast("Этот пост помечен как рекламный");
if (postIds) {
VotesPreferences.saveAdPostInfo(ownerid, postId);
}
} else {
boolean postIds = PostService.isPostAd(ownerid, postId);
if (postIds) {
PostsPreferences.saveAdPostInfo(ownerid, postId);
if (!PostsPreferences.isEnabledMarking()) {
sendToast("Этот пост помечен как рекламный");
}
}
}
});
}
@@ -1,13 +1,23 @@
package ru.vtosters.hooks;
import android.util.Log;
import android.util.SparseArray;
import com.vk.core.network.Network;
import com.vk.im.engine.internal.api_parsers.UserApiParser;
import com.vk.im.engine.models.users.User;
import com.vtosters.lite.R;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.other.Preferences.*;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
@@ -85,22 +95,34 @@ public class OnlineFormatterHook {
if (appname != null) return appname;
makeRequest("https://" + getApi() + "/method/apps.get?app_id=" + appid + "&v=5.99&access_token=" + getUserToken(),
response -> {
try {
JSONObject mainJson = new JSONObject(response);
JSONObject responseJson = mainJson.getJSONObject("response");
JSONArray itemsJson = responseJson.getJSONArray("items");
okhttp3.Request req = new okhttp3.Request.a()
.b("https://" + getApi() + "/method/apps.get?app_id=" + appid + "&v=5.99&access_token=" + getUserToken())
.a();
AppName = itemsJson.getJSONObject(0).optString("title", "");
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
prefs.edit().putString(String.valueOf(appid), AppName).apply();
@Override
public void a(Call call, Response response) throws IOException {
try {
JSONObject mainJson = new JSONObject(response.a().g());
JSONObject responseJson = mainJson.getJSONObject("response");
JSONArray itemsJson = responseJson.getJSONArray("items");
AppName = itemsJson.getJSONObject(0).optString("title", "");
prefs.edit().putString(String.valueOf(appid), AppName).apply();
if (dev()) sendToast("AppName: " + AppName + " for appid: " + appid);
} catch (JSONException e) {
e.fillInStackTrace();
}
}
});
if (dev()) sendToast("AppName: " + AppName + " for appid: " + appid);
} catch (JSONException e) {
e.printStackTrace();
}
});
return AppName;
}
@@ -125,16 +147,36 @@ public class OnlineFormatterHook {
return json;
}
public static JSONArray onlineHookList(JSONArray jsonArr) {
if (!getBoolValue("onlinefix", false) || Preferences.serverFeaturesDisable()) return jsonArr;
public static SparseArray<User> onlineHookList(String str) {
JSONArray jSONArray;
try {
JsonInjectors.setOnlineInfoUsers(jsonArr);
} catch (Exception e) {
Log.e("onlineHookProfiles", e.getMessage());
jSONArray = new JSONObject(str).getJSONArray("response");
} catch (JSONException e) {
throw new RuntimeException(e);
}
SparseArray<User> sparseArray = new SparseArray<>(jSONArray.length());
int length = jSONArray.length();
if (getBoolValue("onlinefix", false) || !Preferences.serverFeaturesDisable()) {
try {
jSONArray = JsonInjectors.setOnlineInfoUsers(jSONArray);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
return jsonArr;
for (int i = 0; i < length; i++) {
JSONObject jSONObject;
try {
jSONObject = jSONArray.getJSONObject(i);
} catch (JSONException e) {
throw new RuntimeException(e);
}
User b = UserApiParser.b(jSONObject);
sparseArray.put(b.getId(), b);
}
return sparseArray;
}
public static JSONObject onlineHookProfiles(JSONObject json) {
@@ -167,19 +209,19 @@ public class OnlineFormatterHook {
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("read_requests").optJSONArray("items"));
} catch (Exception e) {
Log.e("onlineHookItems", e.getMessage());
Log.e("onlineHookItemsRec", e.getMessage());
}
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONObject("recommendations").optJSONArray("items"));
} catch (Exception e) {
Log.e("onlineHookItems", e.getMessage());
Log.e("onlineHookItemsRec", e.getMessage());
}
try {
JsonInjectors.setOnlineInfoUsers(json.optJSONArray("profiles"));
} catch (Exception e) {
Log.e("onlineHookItems", e.getMessage());
Log.e("onlineHookItemsRec", e.getMessage());
}
return json;
@@ -15,6 +15,7 @@ import android.widget.Toast;
import androidx.core.content.FileProvider;
import com.vk.core.dialogs.actionspopup.ActionsPopup;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.network.Network;
import com.vk.core.util.ToastUtils;
import com.vk.dto.common.AttachmentWithMedia;
import com.vk.dto.common.ImageSize;
@@ -90,7 +91,7 @@ public class PhotoViewer {
var req = new Request.a()
.b(getImageUrlFromAttachment(attachment))
.a();
client.a(req).a(new Callback() {
Network.b(Network.ClientType.CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.printStackTrace();
@@ -100,7 +101,7 @@ public class PhotoViewer {
public void a(Call call, Response response)
throws IOException {
var tmpImage = new File(AndroidUtils.getGlobalContext().getExternalCacheDir(), attachment.getId() + ".jpg");
try (var resp = client.a(req).execute();
try (var resp = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER).a(req).execute();
var sink = Okio.a(Okio.b(tmpImage))) {
sink.a(resp.a().f());
var manager = (ClipboardManager) AndroidUtils.getGlobalContext().getSystemService(Context.CLIPBOARD_SERVICE);
@@ -175,4 +176,4 @@ public class PhotoViewer {
}
return max.url;
}
}
}
@@ -0,0 +1,43 @@
package ru.vtosters.hooks;
import android.content.Context;
import android.util.Log;
import com.vk.dto.newsfeed.entries.NewsEntry;
import com.vk.dto.newsfeed.entries.Post;
import com.vk.newsfeed.NewsEntryActionsAdapter;
import com.vk.newsfeed.holders.AdMarkHolder;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.sponsorpost.internal.Native;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
public class PostsMenuHook {
private static final int id = 200;
public static void injectList(NewsEntryActionsAdapter actionsAdapter, NewsEntry newsEntry) {
if (newsEntry instanceof Post && Preferences.isValidSignature() && Native.canVote()) {
int ownerId = ((Post) newsEntry).b();
if (ownerId != AccountManagerUtils.getUserId() && PostsPreferences.isEnabled()) {
actionsAdapter.a(id, "SponsorPost");
}
}
}
public static void injectButtons(int i, NewsEntry newsEntry, Context context) {
if (newsEntry instanceof Post) {
Post post = (Post) newsEntry;
int ownerId = post.b();
int postId = post.P1();
int date = post.K();
AdMarkHolder.voteDialog(ownerId, postId, date, context);
} else {
Log.d("PostsMenuHook", "unsupported instance");
}
}
public static boolean isCustomButton(int i) {
return i == id;
}
}
@@ -26,26 +26,15 @@ import static ru.vtosters.lite.utils.LifecycleUtils.restartApplication;
public class ProxyHook {
public static String linkReplacer(String link) {
var vkapi = "api.vk.com";
var oauth = "oauth.vk.com";
var vkstatic = "static.vk.com";
String vkapi = "api.vk.com";
String oauth = "oauth.vk.com";
String vkstatic = "static.vk.com";
var proxyapi = getString("proxyapi");
var proxyoauth = getString("proxyoauth");
var proxystatic = getString("proxystatic");
String proxyapi = getProxyHost("proxyapi");
String proxyoauth = getProxyHost("proxyoauth");
String proxystatic = getProxyHost("proxystatic");
if (isVikaProxyEnabled()) {
proxyapi = VikaMobile.getApiHost();
proxyoauth = VikaMobile.getOauthHost();
proxystatic = VikaMobile.getStaticHost();
}
if (!isAnyProxyEnabled() || link.isEmpty()) {
return link;
}
if (proxyapi.isEmpty() || proxyoauth.isEmpty() || proxystatic.isEmpty()) {
Log.d("VTLite", "Proxy is not set" + " " + proxyapi + " " + proxyoauth + " " + proxystatic);
if (!isAnyProxyEnabled() || link.isEmpty() || areProxyHostsEmpty(proxyapi, proxyoauth, proxystatic)) {
return link;
}
@@ -64,6 +53,29 @@ public class ProxyHook {
return link;
}
private static String getProxyHost(String hostType) {
if (isVikaProxyEnabled()) {
return switch (hostType) {
case "proxyapi" -> VikaMobile.getApiHost();
case "proxyoauth" -> VikaMobile.getOauthHost();
case "proxystatic" -> VikaMobile.getStaticHost();
default -> "";
};
} else {
return "";
}
}
private static boolean areProxyHostsEmpty(String... hosts) {
for (String host : hosts) {
if (host.isEmpty()) {
Log.d("VTLite", "Proxy is not set: " + host);
return true;
}
}
return false;
}
public static String staticFix(String str) {
var string = getString("proxystatic");
@@ -108,22 +120,18 @@ public class ProxyHook {
RadioButton rgDefault = new RadioButton(new ContextThemeWrapper(ctx, com.vtosters.lite.R.style.Widget_AppCompat_CompoundButton_RadioButton));
RadioButton rgZaborona = new RadioButton(new ContextThemeWrapper(ctx, com.vtosters.lite.R.style.Widget_AppCompat_CompoundButton_RadioButton));
// RadioButton rgRandomProxy = new RadioButton(new ContextThemeWrapper(ctx, com.vtosters.lite.R.style.Widget_AppCompat_CompoundButton_RadioButton));
RadioButton rgVika = new RadioButton(new ContextThemeWrapper(ctx, com.vtosters.lite.R.style.Widget_AppCompat_CompoundButton_RadioButton));
SwitchHook.setCompoundButton(rgDefault);
SwitchHook.setCompoundButton(rgZaborona);
// SwitchHook.setCompoundButton(rgRandomProxy);
SwitchHook.setCompoundButton(rgVika);
rg.addView(rgDefault);
rg.addView(rgVika);
rg.addView(rgZaborona);
// rg.addView(rgRandomProxy);
rgDefault.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp2px(14f));
rgZaborona.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp2px(14f));
// rgRandomProxy.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp2px(14f));
rgVika.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp2px(14f));
rg.setPadding(dp2px(18f), dp2px(12f), dp2px(18f), 0);
@@ -134,28 +142,21 @@ public class ProxyHook {
rgZaborona.setText(ctx.getString(R.string.proxy_enable) + " (Zaborona)");
rgZaborona.setTextColor(getTextAttr());
// rgRandomProxy.setText(ctx.getString(R.string.proxy_enable) + " (Random Socks Proxy)");
// rgRandomProxy.setTextColor(getTextAttr());
rgVika.setText(ctx.getString(R.string.proxy_enable) + " (Vika Mobile)");
rgVika.setTextColor(getTextAttr());
rgZaborona.setChecked(isZaboronaEnabled());
rgDefault.setChecked(!isZaboronaEnabled() && !isRandomProxyEnabled() && !isVikaProxyEnabled());
// rgRandomProxy.setChecked(isRandomProxyEnabled());
rgDefault.setChecked(!isZaboronaEnabled() && !isVikaProxyEnabled());
rgVika.setChecked(isVikaProxyEnabled());
new VkAlertDialog.Builder(ctx)
.setTitle(ctx.getString(R.string.vtlproxy))
.setMessage(ctx.getString(R.string.proxy_warning))
.setMessage(ctx.getString(R.string.proxy_warning) + "\n\nТак же можно включить встроенный прокси от ВКонтакте перейдя в настройки прокси")
.setView(rg)
.setPositiveButton(ctx.getString(R.string.vtl_confirm), ((dialog, which) -> { // Применить
if (rgZaborona.isChecked()) {
Preferences.getPreferences().edit().putString("proxy", "zaborona").commit();
restartApplication();
// } else if (rgRandomProxy.isChecked()) {
// Preferences.getPreferences().edit().putString("proxy", "randomproxy").commit();
// restartApplication();
} else if (rgVika.isChecked()) {
Preferences.getPreferences().edit().putString("proxy", "vika").commit();
restartApplication();
@@ -169,4 +170,4 @@ public class ProxyHook {
})
.show();
}
}
}
@@ -0,0 +1,29 @@
package ru.vtosters.hooks;
import android.os.Build;
import me.leolin.shortcutbadger.Badger;
import me.leolin.shortcutbadger.impl.*;
import java.util.List;
public class ShortcutBadgerHook {
public static void init(List<Class<? extends Badger>> list) {
list.add(AdwHomeBadger.class);
list.add(ApexHomeBadger.class);
list.add(DefaultBadger.class);
list.add(NewHtcHomeBadger.class);
list.add(NovaHomeBadger.class);
list.add(AsusHomeBadger.class);
list.add(HuaweiHomeBadger.class);
list.add(OPPOHomeBader.class);
list.add(SamsungHomeBadger.class);
list.add(ZukHomeBadger.class);
list.add(VivoHomeBadger.class);
list.add(ZTEHomeBadger.class);
list.add(EverythingMeHomeBadger.class);
if (Build.VERSION.SDK_INT < 34) {
list.add(SonyHomeBadger.class);
}
}
}
@@ -3,15 +3,17 @@ package ru.vtosters.hooks;
import com.vk.medianative.MediaImageEncoder;
import com.vk.toggle.FeatureManager;
import com.vk.toggle.Features;
import ru.vtosters.hooks.other.Preferences;
import static ru.vtosters.hooks.other.Preferences.*;
public class TogglesHook {
public static boolean shouldPatch(Features.Type feature) {
return switch (feature) {
case AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL, AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE ->
true;
default -> false;
default ->
false;
};
}
@@ -23,9 +25,10 @@ public class TogglesHook {
case FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED -> !milkshake();
case AB_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_COMPACT_REPOST, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT ->
postsredesign();
case FEATURE_IMAGE_QUALITY_UPGRADE -> MediaImageEncoder.needToSkipCompression();
case FEATURE_IMAGE_QUALITY_UPGRADE -> !MediaImageEncoder.needToCompress();
case FEATURE_MENU_GAMES_CAROUSEL -> miniapps();
case FEATURE_TRAFFIC_SAVER -> !disableForceTrafficSaver();
case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL -> Preferences.getBoolValue("batchmessages", false);
default -> true;
};
}
@@ -24,9 +24,7 @@ public class VKUIHook {
private static void applyVKUIStyles(WebView webView) {
if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) {
if (!WebViewColoringUtils.isLoaded) {
WebViewColoringUtils.load();
}
WebViewColoringUtils.load();
loadAndApplyCSS(webView);
}
}
@@ -9,6 +9,7 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.VKXUtils;
@@ -19,18 +20,29 @@ import static ru.vtosters.hooks.other.Preferences.milkshake;
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
public class MusicBottomSheetHook {
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions, final MusicTrack musicTrack) {
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
if (musicTrack.F1()) return actions;
final var trackId = asId(musicTrack);
String trackId = asId(musicTrack);
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
if (isIntegrationEnabled()) {
if (VKXUtils.isVkxCached(trackId)) actions.add(getRemoveCacheTrackVkxAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackVkxAction());
if (VKXUtils.isVkxCached(trackId)) {
actions.add(getRemoveCacheTrackVkxAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
}
} else {
if (MusicCacheImpl.isCachedTrack(trackId)) actions.add(getRemoveCacheTrackAction());
else if (isNetworkConnected()) actions.add(addToCacheTrackAction());
if (MusicCacheImpl.isCachedTrack(trackId)) {
actions.add(getRemoveCacheTrackAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
return actions;
}
@@ -72,8 +84,12 @@ public class MusicBottomSheetHook {
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackVkxAction());
}
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
} else {
if (PlaylistCacheDbDelegate.isCachedPlaylist(AndroidUtils.getGlobalContext(), playlist.v1())) {
actions.add(getRemoveCacheTrackAction());
} else if (isNetworkConnected()) {
actions.add(addToCacheTrackAction());
}
}
if (isNetworkConnected()) actions.add(downloadAsMp3Action());
@@ -134,20 +150,24 @@ public class MusicBottomSheetHook {
}
public static boolean injectOnClick(int actionId, Playlist playlist) { // playlist inj
if (actionId == R.id.play_in_vkx)
if (actionId == R.id.play_in_vkx) {
return tryPlayInVKX(null, null, playlist);
if (actionId == R.id.add_to_cache) {
}
if (actionId == R.id.remove_from_cache) {
PlaylistCacheDbDelegate.deletePlaylist(AndroidUtils.getGlobalContext(), playlist.v1());
return true;
} else if (actionId == R.id.add_to_cache) {
AudioDownloader.cachePlaylist(playlist);
return true;
}
if (actionId == R.id.remove_from_cache_vkx) {
getInstance().runOnService(service -> {
try {
service.deletePlaylistFromCache(playlist.a, playlist.b);
} catch (RemoteException e) {
e.printStackTrace();
e.fillInStackTrace();
}
});
return true;
@@ -156,7 +176,7 @@ public class MusicBottomSheetHook {
try {
service.addPlaylistToCache(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.printStackTrace();
e.fillInStackTrace();
}
});
return true;
@@ -168,7 +188,7 @@ public class MusicBottomSheetHook {
try {
service.downloadPlaylist(playlist.a, playlist.b, (playlist.Q != null) ? playlist.Q : "");
} catch (RemoteException e) {
e.printStackTrace();
e.fillInStackTrace();
}
});
} else {
@@ -258,4 +278,4 @@ public class MusicBottomSheetHook {
false
);
}
}
}
@@ -5,19 +5,22 @@ import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.Objects;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.DateHook.getLocale;
import static ru.vtosters.hooks.GroupsCatalogInjector.injectIntoCatalog;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
@@ -26,8 +29,6 @@ import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
public class CatalogJsonInjector {
private static final OkHttpClient mClient = VtOkHttpClient.getInstance();
public static JSONObject music(JSONObject json) throws JSONException {
var catalog = json.optJSONObject("catalog");
var oldItems = catalog != null ? catalog.optJSONArray("sections") : null;
@@ -51,19 +52,13 @@ public class CatalogJsonInjector {
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
var noPlaylists = !json.has("playlists");
if (noPlaylists) {
json.put("playlists", new JSONArray().put(getPlaylist()));
Log.d("catalogInjector", "added new pl");
} else {
json.optJSONArray("playlists").put(getPlaylist());
Log.d("catalogInjector", "added to exist pl");
}
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists);
if (!useOldAppVer || noPlaylists) {
var newBlocks = new JSONArray();
newBlocks
.put(getCatalogHeader())
.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogPlaylist())
.put(getCatalogSeparator());
@@ -90,6 +85,7 @@ public class CatalogJsonInjector {
var blocks = section.getJSONArray("blocks");
fixDailyMix(blocks);
removeUnsupportedLayouts(blocks);
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
return; // early return if not users catalog or no tracks or integration enabled
@@ -97,23 +93,13 @@ public class CatalogJsonInjector {
var noPlaylists = !json.has("playlists");
if (noPlaylists) {
json.put("playlists", new JSONArray().put(getPlaylist()));
Log.d("catalogInjector", "added new pl");
} else {
try {
json.optJSONArray("playlists").put(getPlaylist());
Log.d("catalogInjector", "added to exist pl");
} catch (Exception e) {
Log.d("catalogInjector", e.toString());
}
}
PlaylistHelper.addCachedPlaylists(json.optJSONArray("playlists"), noPlaylists);
if (!useOldAppVer && !noPlaylists) {
var newBlocks = new JSONArray();
newBlocks
.put(getCatalogHeader())
.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogPlaylist())
.put(getCatalogSeparator());
@@ -131,12 +117,15 @@ public class CatalogJsonInjector {
if (type.equals("music_playlists") && j.has("playlists_ids")) {
var newarr = new JSONArray();
var playlists_ids = j.optJSONArray("playlists_ids");
var playlists_ids = j.getJSONArray("playlists_ids");
var savedPlaylists = PlaylistHelper.getCachedPlaylistsIds();
newarr.put(getUserId() + "_-1");
for (int n = 0; n < savedPlaylists.length(); n++) {
newarr.put(savedPlaylists.getString(n));
}
for (int n = 0; n < playlists_ids.length(); n++) {
newarr.put(playlists_ids.optString(n));
newarr.put(playlists_ids.getString(n));
}
Log.d("catalogInjector", "added to pl ids");
@@ -219,19 +208,33 @@ public class CatalogJsonInjector {
}
private static void removeUnsupportedLayouts(JSONArray blocks) throws JSONException {
for (int i = 0; i < blocks.length(); i++) {
var block = blocks.optJSONObject(i);
var layout = block.optJSONObject("layout");
if (layout != null) {
var name = layout.optString("name");
if (name.equals("header_extended")) {
if (layout.has("top_title")) blocks.remove(i);
layout.put("name", "header");
}
for (int i = blocks.length() - 1; i >= 0; i--) {
JSONObject block = blocks.optJSONObject(i);
JSONObject layout = block.optJSONObject("layout");
String data_type = block.optString("data_type");
if (Objects.equals(data_type, "music_recommended_playlists") || Objects.equals(data_type, "radiostations")) {
blocks.remove(i); // block
if (i - 1 >= 0) blocks.remove(i - 1); // text
if (i - 2 >= 0) blocks.remove(i - 2); // divider
continue; // Skip to the next iteration after removing
}
if (layout == null) continue; // Skip to the next iteration if layout is null
String name = layout.optString("name");
if (!name.equals("header_extended")) {
continue; // Skip to the next iteration if not header_extended
}
if (layout.has("top_title")) {
blocks.remove(i);
}
layout.put("name", "header");
}
}
private static void setDefaultAudioPage(JSONArray jsonArray, JSONObject catalog) throws JSONException {
for (int i = 0; i < jsonArray.length(); i++) {
var item = jsonArray.optJSONObject(i);
@@ -263,9 +266,12 @@ public class CatalogJsonInjector {
+ sectionUrl
+ "&access_token="
+ AccountManagerUtils.getUserToken())
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
var json = new JSONObject(mClient.a(request).execute().a().g()).getJSONObject("response");
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
var json = new JSONObject(GzipDecompressor.decompressResponse(resp)).getJSONObject("response");
var catalogarr = json.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
var title = catalogarr.optString("title");
@@ -279,4 +285,4 @@ public class CatalogJsonInjector {
Log.d("VTLMusic", "Error: " + e.getMessage());
}
}
}
}
@@ -18,15 +18,16 @@ import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PlaylistInjector {
public final static String CHANNEL_NAME = "VTCH";
private static final ExecutorService executor = Executors.newCachedThreadPool();
public final static String CHANNEL_NAME = "VTCH";
public static void injectDownloadPlaylist(Playlist playlist) {
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
AudioDownloader.cachePlaylist(playlist);
}
public static boolean eligibleForOfflineCaching() {
@@ -35,7 +36,7 @@ public class PlaylistInjector {
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
try {
if (!eligibleForOfflineCaching())
if (MusicCacheImpl.isEmpty())
return null;
var requestArgs = audioGetPlaylist.b();
@@ -45,8 +46,6 @@ public class PlaylistInjector {
boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache"));
boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum"));
Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist);
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist))
return null;
@@ -73,7 +72,7 @@ public class PlaylistInjector {
response.c = tracks;
} catch (RemoteException | JSONException e) {
e.printStackTrace();
e.fillInStackTrace();
response.c = new ArrayList<>();
}
observableEmitter.b(response);
@@ -82,18 +81,20 @@ public class PlaylistInjector {
return;
}
if (isVirtualPlaylist) {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getMyCachedMusicTracks();
response.c = (ArrayList<MusicTrack>) MusicCacheImpl.getPlaylistSongs(ownerId, id);
Log.d("Playlist", "Open playlist " + ownerId + "_" + id);
if (Objects.equals(id, "-1")) {
response.b = PlaylistHelper.createCachedPlaylistMetadata();
} else {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks(requestArgs.get("owner_id"));
response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id"));
response.b = MusicCacheImpl.getPlaylist(id, ownerId);
}
observableEmitter.b(response);
});
} catch (Exception e) {
e.printStackTrace();
e.fillInStackTrace();
}
return null;
}
@@ -12,6 +12,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
@@ -22,6 +23,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.*;
public class TracklistInjector {
public static boolean eligibleForOfflineCaching() {
@@ -59,7 +62,9 @@ public class TracklistInjector {
if (MusicCacheImpl.isEmpty()) {
observableEmitter.b(parser.c(getEmptyCatalog()));
} else {
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
observableEmitter.b(parser.c(createVirtualCatalog(
PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(),
AccountManagerUtils.getUserId() + "_-1"))));
}
}
});
@@ -162,16 +167,12 @@ public class TracklistInjector {
boolean noPlaylists = hasNoPlaylists(blocks);
if (noPlaylists) {
catalogNode.put("playlists", new JSONArray().put(PlaylistHelper.getPlaylist()));
PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), true);
JSONArray newBlocks = createNewBlocks(blocks);
Log.d("catalogInjector", "added cache catalog playlist");
firstSection.put("blocks", newBlocks);
firstSection.put("blocks", createNewBlocks(blocks));
} else {
try {
catalogNode.optJSONArray("playlists").put(PlaylistHelper.getPlaylist());
PlaylistHelper.addCachedPlaylists(catalogNode.optJSONArray("playlists"), false);
updatePlaylistsIds(blocks);
} catch (Exception e) {
@@ -188,7 +189,7 @@ public class TracklistInjector {
return firstSection.getString("url").equals("https://vk.com/audios" + AccountManagerUtils.getUserId() + "?section=all");
}
private static boolean hasNoPlaylists(JSONArray blocks) throws JSONException {
private static boolean hasNoPlaylists(JSONArray blocks) {
return blocks.optJSONObject(0).optJSONArray("buttons").optJSONObject(0).optJSONObject("action").optString("type").equals("create_playlist");
}
@@ -196,13 +197,14 @@ public class TracklistInjector {
JSONArray newBlocks = new JSONArray();
newBlocks
.put(PlaylistHelper.getCatalogHeader())
.put(PlaylistHelper.getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
.put(PlaylistHelper.getCatalogPlaylist())
.put(PlaylistHelper.getCatalogSeparator());
for (int i = 0; i < blocks.length(); i++) {
newBlocks.put(blocks.optJSONObject(i));
}
return newBlocks;
}
@@ -212,16 +214,15 @@ public class TracklistInjector {
String type = j.optString("data_type");
if (type.equals("music_playlists") && j.has("playlists_ids")) {
JSONArray newarr = new JSONArray();
JSONArray newarr = PlaylistHelper.getCachedPlaylistsIds();
JSONArray playlists_ids = j.optJSONArray("playlists_ids");
newarr.put(AccountManagerUtils.getUserId() + "_-1");
for (int n = 0; n < playlists_ids.length(); n++) {
newarr.put(playlists_ids.optString(n));
}
j.put("playlists_ids", newarr);
break;
}
}
@@ -240,7 +241,19 @@ public class TracklistInjector {
audiosBlock.put("layout", layout);
var blocks = new JSONArray().put(getShuffleButton()).put(audiosBlock);
var blocks = new JSONArray();
if (MusicCacheImpl.hasPlaylist()) {
blocks.put(getCatalogHeader("Кешированные плейлисты"))
.put(getCatalogPlaylist());
}
if (!MusicCacheImpl.getAllOwnTracks().isEmpty()) {
if (MusicCacheImpl.hasPlaylist()) blocks.put(getCatalogSeparator());
blocks.put(getCatalogHeader(AndroidUtils.getString(R.string.cached_tracks_title)))
.put(getShuffleButton())
.put(audiosBlock);
}
var randomId = getRandomId();
@@ -259,6 +272,7 @@ public class TracklistInjector {
return new JSONObject()
.put("catalog", catalog)
.put("playlists", addCachedPlaylists(new JSONArray(), false))
.put("audios", TracklistHelper.tracksToJsons(tracks));
}
@@ -6,6 +6,8 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.StrictMode;
import com.vk.core.util.AppContextHolder;
import com.vk.medianative.MediaImageEncoder;
import com.vk.medianative.MediaNative;
import com.vtosters.lite.data.Users;
@@ -22,7 +24,8 @@ import java.security.NoSuchAlgorithmException;
public class Preferences {
public static void init(Application application) throws Exception {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
AppContextHolder.a = application.getApplicationContext();
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy); // fix profiles hide hook
MediaNative.init(application);
@@ -311,6 +314,10 @@ public class Preferences {
return false;
}
public static String getId() {
return String.valueOf(AccountManagerUtils.getUserId());
}
public static void updateBuildNumber() {
try {
getPreferences().edit().putLong("setupTime", AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo(AndroidUtils.getPackageName(), 0).lastUpdateTime).apply();
@@ -352,7 +359,14 @@ public class Preferences {
}
public static int compress(int origquality) {
if (MediaImageEncoder.needToSkipCompression()) return 100;
return origquality;
return MediaImageEncoder.needToCompress() ? origquality : 100;
}
public static String metadataSeparator() {
return getPreferences().getString("metadata_separator", "; ");
}
public static void setMetadataSeparator(String separator) {
getPreferences().edit().putString("metadata_separator", separator).apply();
}
}
@@ -24,6 +24,8 @@ import com.vk.core.drawable.RecoloredDrawable;
import com.vk.core.ui.themes.MilkshakeHelper;
import com.vk.core.ui.themes.VKTheme;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.im.engine.models.dialogs.DialogTheme;
import com.vk.im.ui.themes.DefaultThemeProvider;
import com.vtosters.lite.R;
import com.vtosters.lite.data.ThemeTracker;
import ru.vtosters.lite.deviceinfo.OEMDetector;
@@ -32,7 +34,6 @@ import ru.vtosters.lite.themes.ThemesHacks;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.wallpapers.WallpapersHooks;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.WebViewColoringUtils;
import java.lang.reflect.Field;
@@ -44,7 +45,7 @@ public class ThemesUtils {
try {
setTheme(theme, LifecycleUtils.getCurrentActivity(), restartActivity);
} catch (Exception e) {
e.printStackTrace();
e.fillInStackTrace();
}
} // Apply VKTheme and ImTheme (hard applying without dynamic theme changing)
@@ -61,14 +62,27 @@ public class ThemesUtils {
activity = LifecycleUtils.getCurrentActivity();
}
VKThemeHelper.theme(theme, activity, fl);
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear();
if (restartActivity) activity.recreate();
if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) {
ThemesCore.clear();
ThemesCore.init();
DialogTheme.d.a(new DefaultThemeProvider(VKThemeHelper.k).b()); // reset dialog attrs cache
}
ThemeTracker.a();
WebViewColoringUtils.isLoaded = false;
if (restartActivity) {
activity.recreate();
}
new WebView(activity).clearCache(true);
WebCachePreloader.e();
}
public static DialogTheme getDialogTheme() {
if (isMonetTheme()) {
return new DefaultThemeProvider(VKThemeHelper.k).b(); // get directly from theme cuz monet issue
} else {
return DialogTheme.d.a(); // get cached theme
}
}
public static boolean isDarkTheme() {
return VKThemeHelper.r();
}
@@ -341,7 +355,7 @@ public class ThemesUtils {
}
public static int getColor(int i) {
return ThemesHacks.getHackedColor(LifecycleUtils.getCurrentActivity(), i);
return ThemesHacks.getHackedColor(getGlobalContext(), i);
} // Android Support color injector + accent color checker
public static int getAlertStyle() {
@@ -1,10 +1,11 @@
package ru.vtosters.lite.concurrent;
import com.vk.core.concurrent.VkExecutors;
import io.reactivex.Scheduler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import io.reactivex.Scheduler;
public class VTExecutors {
public static ExecutorService getMusicDownloadExecutor() {
@@ -32,7 +33,21 @@ public class VTExecutors {
}
private static class LazyHolder {
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
private static final int MIN_PARALLELISM = 1;
private static final int MAX_PARALLELISM = 7;
private static final int PARALLELISM = clamp(
(Runtime.getRuntime().availableProcessors() >> 1) - 1
);
private static final ExecutorService INSTANCE =
new ForkJoinPool(PARALLELISM,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
(t, e) -> {},
/* FIFO */ true);
private static int clamp(int value) {
return Math.min(LazyHolder.MAX_PARALLELISM, Math.max(value, LazyHolder.MIN_PARALLELISM));
}
}
}
}
@@ -10,4 +10,4 @@ public class VtOkHttpClient extends OkHttpClient {
private static class LazyHolder {
private static final VtOkHttpClient instance = new VtOkHttpClient();
}
}
}
@@ -6,7 +6,6 @@ import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.engine.utils.ImDialogsUtils;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.net.Request;
@@ -15,21 +14,49 @@ import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import java.io.IOException;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class Requests {
public static void hookRead(Dialog dialog) {
Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.F1() + "&peer_id=" + dialog.getId() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> {
});
okhttp3.Request req = new okhttp3.Request.a()
.b("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.F1() + "&peer_id=" + dialog.getId() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken())
.a();
try {
Network.b(CLIENT_API).a(req).execute().close();;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void hookReadStartMsgTo(Msg dialog) {
Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.C1() + "&peer_id=" + dialog.v1() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> {
});
okhttp3.Request req = new okhttp3.Request.a()
.b("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.C1() + "&peer_id=" + dialog.v1() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken())
.a();
try {
Network.b(CLIENT_API).a(req).execute().close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void hookKick(Msg dialog) {
if (dialog.getFrom().t1() == AccountManagerUtils.getUserId()) return;
Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.removeChatUser?member_id=" + dialog.getFrom().t1() + "&chat_id=" + ImDialogsUtils.c(dialog.v1()) + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> {
});
okhttp3.Request req = new okhttp3.Request.a()
.b("https://" + ProxyUtils.getApi() + "/method/messages.removeChatUser?member_id=" + dialog.getFrom().t1() + "&chat_id=" + ImDialogsUtils.c(dialog.v1()) + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken())
.a();
try {
Network.b(CLIENT_API).a(req).execute().close();;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void hookDialogInfo(Dialog dialog) {
@@ -51,7 +78,7 @@ public class Requests {
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8"))
.a();
new OkHttpClient().a(request).execute().a().g();
Network.b(CLIENT_API).a(request).execute().a().g();
} catch (Exception e) {
e.printStackTrace();
}
@@ -1,27 +1,36 @@
package ru.vtosters.lite.downloaders;
import android.os.Environment;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.util.ToastUtils;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import org.json.JSONObject;
import ru.vtosters.hooks.music.MusicCacheFilesHook;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
import ru.vtosters.lite.music.downloader.AudioGet;
import ru.vtosters.lite.music.downloader.PlaylistDownloader;
import ru.vtosters.lite.music.downloader.ThumbnailPlaylistDownloader;
import ru.vtosters.lite.music.downloader.TrackDownloader;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.List;
import java.util.Objects;
import static ru.vtosters.lite.utils.AndroidUtils.getString;
@@ -32,19 +41,18 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString;
*/
public class AudioDownloader {
public static final ExecutorService executor = Executors.newCachedThreadPool();
public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC;
public static void downloadPlaylist(Playlist playlist) {
var tracks = PlaylistConverter.getPlaylist(playlist);
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
var playlistName = IOUtils.getValidFileName(playlist.g);
String playlistName = IOUtils.getValidFileName(playlist.g);
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
String downloadPath = musicPath + File.separator + playlistName;
var notificationId = playlistName.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
int notificationId = playlistName.hashCode();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
PlaylistDownloader.downloadPlaylist(
tracks,
@@ -60,54 +68,96 @@ public class AudioDownloader {
}
public static void cacheTrack(MusicTrack track) {
var trackId = LibVKXClient.asId(track);
String trackId = LibVKXClient.asId(track);
if (MusicCacheImpl.isCachedTrack(trackId)) {
MusicCacheImpl.removeTrack(trackId);
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
return;
} else {
File trackFile = MusicCacheFilesHook.getTrackFile(trackId);
if (!trackFile.exists()) {
trackFile.getParentFile().mkdirs();
}
downloadM3U8(track, true);
}
var trackFile = MusicCacheFilesHook.getTrackFile(trackId);
if (!trackFile.exists())
trackFile.getParentFile().mkdirs();
executor.submit(() -> downloadM3U8(track, true));
}
public static void cachePlaylist(Playlist playlist) {
var tracks = PlaylistConverter.getPlaylist(playlist);
List<MusicTrack> tracks = PlaylistConverter.getPlaylist(playlist);
var notificationId = playlist.g.hashCode();
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
int notificationId = playlist.g.hashCode();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
JSONObject thumbs = PlaylistUtils.getThumb(playlist);
if (thumbs != null) {
new ThumbnailPlaylistDownloader().download(null, new Callback() {
@Override
public void onProgress(int progress) {
}
@Override
public void onSuccess() {
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
Log.d("Playlist", "adding to cache with thumbs " + playlist.v1());
}
@Override
public void onFailure(Throwable e) {
throw new RuntimeException(e);
}
@Override
public void onSizeReceived(long size, long header) {
}
}, playlist);
} else {
PlaylistCacheDbDelegate.addPlaylist(AndroidUtils.getGlobalContext(), playlist);
Log.d("Playlist", "adding to cache without thumbs " + playlist.a);
}
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
playlist
);
}
public static void cacheAllAudios() {
var tracks = AudioGet.getAudios();
List<MusicTrack> tracks = AudioGet.getAudios();
var notificationId = AccountManagerUtils.getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
int notificationId = AccountManagerUtils.getUserId();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
if (tracks == null || tracks.isEmpty()) {
return;
}
PlaylistDownloader.cachePlaylist(
tracks,
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId),
PlaylistHelper.createCachedPlaylistMetadata()
);
}
public static void downloadAllAudios() {
var tracks = AudioGet.getAudios();
List<MusicTrack> tracks = AudioGet.getAudios();
var notificationId = AccountManagerUtils.getUserId();
var notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
int notificationId = AccountManagerUtils.getUserId();
NotificationCompat.Builder notification = MusicNotificationBuilder.buildAllAudiosDownloadNotification(notificationId);
var playlistName = "Audios of " + AccountManagerUtils.getUserId();
String playlistName = "Audios of " + AccountManagerUtils.getUserId();
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var downloadPath = musicPath + File.separator + playlistName;
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
String downloadPath = musicPath + File.separator + playlistName;
if (tracks == null || tracks.isEmpty()) {
return;
}
PlaylistDownloader.downloadPlaylist(
tracks,
@@ -118,20 +168,20 @@ public class AudioDownloader {
}
private static void downloadM3U8(MusicTrack track, boolean cache) {
if (track.D == null) {
Objects.requireNonNull(track.D, () -> {
ToastUtils.a(getString(R.string.link_audio_error));
return;
}
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
var tempId = track.d;
var downloadPath = musicPath + File.separator;
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
return "link must not be null";
});
String musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
int tempId = track.d;
String downloadPath = musicPath + File.separator;
NotificationCompat.Builder notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
Callback cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification);
if (cache) {
TrackDownloader.cacheTrack(track, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
TrackDownloader.cacheTrack(track, cb, PlaylistHelper.createCachedPlaylistMetadata());
} else {
TrackDownloader.downloadTrack(track, downloadPath, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
TrackDownloader.downloadTrack(track, downloadPath, cb);
}
}
}
}
@@ -1,29 +1,26 @@
package ru.vtosters.lite.downloaders;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.media.MediaScannerConnection;
import android.os.Environment;
import com.vk.core.network.Network;
import com.vk.dto.common.ImageSize;
import com.vk.dto.stories.model.StoryEntry;
import okhttp3.*;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import static ru.vtosters.lite.downloaders.VideoDownloader.downloadVideo;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class StoryDownloader {
public static void downloadStory(StoryEntry story) {
if (story.T1()) {
List<ImageSize> imageSizes = story.E.Q.t1();
String url = imageSizes.get(imageSizes.size() - 1).url;
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("photo" + story.E.a + ".jpg");
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "photo" + story.E.a + ".jpg");
request.allowScanningByMediaScanner();
((DownloadManager) getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
downloadImage(story);
return;
}
// if story is a video
@@ -31,4 +28,53 @@ public class StoryDownloader {
downloadVideo(story.F, null);
}
}
}
public static void downloadImage(StoryEntry story) {
List<ImageSize> imageSizes = story.E.Q.t1();
String url = imageSizes.get(imageSizes.size() - 1).url;
OkHttpClient client = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER);
Request request = new Request.a()
.b(url)
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) {
ResponseBody responseBody = response.a();
String fileName = "photo" + story.E.a + ".jpg";
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/Stories/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, fileName);
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
MediaScannerConnection.scanFile(AndroidUtils.getGlobalContext(), new String[]{outputFile.toString()}, null, null);
AndroidUtils.sendToast("Файл скачан в Pictures/Stories");
} catch (IOException e) {
e.fillInStackTrace();
}
}
});
}
}
@@ -1,9 +1,11 @@
package ru.vtosters.lite.downloaders;
import android.app.DownloadManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
@@ -11,37 +13,159 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.dialogs.bottomsheet.MenuBottomSheetAction;
import com.vk.core.network.Network;
import com.vk.core.util.ToastUtils;
import com.vk.dto.common.VideoFile;
import com.vk.dto.stories.model.StoryEntry;
import com.vtosters.lite.R;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class VideoDownloader {
public static final int DOWNLOAD_ID = 0;
public static final int OPEN_EXTERNAL_LINK_ID = 1;
public static int DOWNLOAD_ID = 0;
public static int OPEN_EXTERNAL_LINK_ID = 1;
public static String channelId = "video_download_channel";
public static String channelName = "Video Download Channel";
public static int notificationId = 1;
public static void addAction(List<MenuBottomSheetAction> actions, int... ints) {
actions.add(new MenuBottomSheetAction(ints[0], ints[1], ints[2], ints[3]));
}
public static void downloadVideo(VideoFile videoFile, Context context) {
final List<String> qualities = new ArrayList<>();
final List<String> urls = new ArrayList<>();
List<String> qualities = new ArrayList<>();
List<String> urls = new ArrayList<>();
addVideos(videoFile, qualities, urls);
Context finalContext = context != null ? context : LifecycleUtils.getCurrentActivity();
if (qualities.isEmpty()) {
ToastUtils.a(finalContext.getString(R.string.videodownloaderr));
return;
}
var adapter = new ArrayAdapter(
finalContext,
android.R.layout.simple_list_item_1,
qualities.toArray(new String[0])) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
};
new VkAlertDialog.Builder(finalContext)
.setAdapter(adapter, (dialog, which) -> {
String url = urls.get(which);
NotificationManager notificationManager = (NotificationManager) AndroidUtils.getGlobalContext().getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(notificationChannel);
}
String notificationTitle = "Загрузка видео";
String notificationText = "Загрузка началась";
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(AndroidUtils.getGlobalContext(), channelId)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setProgress(0, 0, true)
.setOngoing(true);
notificationManager.notify(notificationId, notificationBuilder.build());
OkHttpClient client = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER);;
Request request = new Request.a()
.b(url)
.a();
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) {
ResponseBody responseBody = response.a();
File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "/VKVideo/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, videoFile + ".mp4");
try (InputStream inputStream = responseBody.a();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
long fileSize = responseBody.d();
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
int progress = (int) ((totalBytesRead * 100) / fileSize);
notificationBuilder.setProgress(100, progress, false)
.setContentText("Загружено " + progress + "%");
notificationManager.notify(notificationId, notificationBuilder.build());
}
outputStream.flush();
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
notificationManager.cancel(notificationId);
String successMessage = "Файл успешно скачан";
notificationBuilder.setContentText(successMessage)
.setProgress(0, 0, false)
.setContentTitle(notificationTitle)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setOngoing(false);
notificationManager.notify(notificationId, notificationBuilder.build());
} catch (IOException e) {
e.fillInStackTrace();
}
}
});
})
.show();
}
public static void addVideos(VideoFile videoFile, List<String> qualities, List<String> urls) {
if (!TextUtils.isEmpty(videoFile.D)) {
qualities.add("2160p");
urls.add(videoFile.D);
@@ -75,40 +199,6 @@ public class VideoDownloader {
qualities.add("240p");
urls.add(videoFile.e);
}
Context finalContext = context != null ? context : LifecycleUtils.getCurrentActivity();
if (qualities.isEmpty()) {
ToastUtils.a(finalContext.getString(R.string.videodownloaderr));
return;
}
var adapter = new ArrayAdapter(
finalContext,
android.R.layout.simple_list_item_1,
qualities.toArray(new String[0])) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
};
new VkAlertDialog.Builder(finalContext)
.setAdapter(adapter, (dialog, which) -> {
try {
var url = urls.get(which);
var request = new DownloadManager.Request(Uri.parse(url))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(videoFile.toString())
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MOVIES, videoFile + ".mp4");
request.allowScanningByMediaScanner();
((DownloadManager) finalContext.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
} catch (Throwable ignored) {
}
})
.show();
}
public static void parseVideoLink(String url, Context ctx) {
@@ -124,20 +214,35 @@ public class VideoDownloader {
progressDialog.setMessage(ctx.getString(R.string.video_dl_progress));
progressDialog.show();
makeRequest("https://" + getApi() + "/method/stories.getById?stories=" + storyId + "&v=5.99&access_token=" + getUserToken(),
response -> {
progressDialog.cancel();
var req = new Request.a()
.b("https://" + getApi() + "/method/stories.getById?stories=" + storyId + "&v=5.99&access_token=" + getUserToken())
.a("Accept-Encoding", "gzip")
.a();
Network.b(CLIENT_API).a(req).a(new Callback() {
try {
JSONObject mainJson = new JSONObject(response);
JSONObject responseJson = mainJson.getJSONObject("response").optJSONArray("items").optJSONObject(0);
StoryEntry story = new StoryEntry(responseJson);
StoryDownloader.downloadStory(story);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
});
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
@Override
public void a(Call call, Response response) throws IOException {
progressDialog.cancel();
try {
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
JSONObject responseJson = mainJson.getJSONObject("response").optJSONArray("items").optJSONObject(0);
StoryEntry story = new StoryEntry(responseJson);
StoryDownloader.downloadStory(story);
} catch (JSONException e) {
e.fillInStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
}
});
return;
}
@@ -149,25 +254,38 @@ public class VideoDownloader {
String videoId = url.split("video")[1];
String ownerId = videoId.split("_")[0];
final ProgressDialog progressDialog = new ProgressDialog(ctx);
ProgressDialog progressDialog = new ProgressDialog(ctx);
progressDialog.setMessage(ctx.getString(R.string.video_dl_progress));
progressDialog.show();
makeRequest("https://" + getApi() + "/method/video.get?owner_id=" + ownerId + "&videos=" + videoId + "&v=5.99&access_token=" + getUserToken(),
response -> {
progressDialog.cancel();
var req = new Request.a()
.b("https://" + getApi() + "/method/video.get?owner_id=" + ownerId + "&videos=" + videoId + "&v=5.99&access_token=" + getUserToken())
.a("Accept-Encoding", "gzip")
.a();
try {
JSONObject mainJson = new JSONObject(response);
JSONObject responseJson = mainJson.getJSONObject("response");
JSONArray itemsJson = responseJson.getJSONArray("items");
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
VideoFile videoFile = new VideoFile(itemsJson.getJSONObject(0));
downloadVideo(videoFile, ctx);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
});
@Override
public void a(Call call, Response response) throws IOException {
progressDialog.cancel();
try {
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
JSONObject responseJson = mainJson.getJSONObject("response");
JSONArray itemsJson = responseJson.getJSONArray("items");
VideoFile videoFile = new VideoFile(itemsJson.getJSONObject(0));
downloadVideo(videoFile, ctx);
} catch (JSONException e) {
e.fillInStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
}
});
}
}
}
@@ -3,6 +3,7 @@ package ru.vtosters.lite.downloaders.messages;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.vk.core.network.Network;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.R;
import okhttp3.Call;
@@ -12,12 +13,12 @@ import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.messages.items.MiniMsg;
import ru.vtosters.lite.downloaders.messages.items.MiniUser;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,6 +29,8 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
public class MessagesDownloader {
private static final int[] VIDEO_QUALITIES = {144, 240, 360, 480, 720, 1080, 1440, 2160};
@@ -104,8 +107,9 @@ public class MessagesDownloader {
AccountManagerUtils.getUserToken());
var req = new Request.a()
.b(url)
.a("Accept-Encoding", "gzip")
.a();
VtOkHttpClient.getInstance().a(req).a(new Callback() {
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
Log.d("MessagesDownloader", e.getMessage());
@@ -114,7 +118,7 @@ public class MessagesDownloader {
@Override
public void a(Call call, Response response) {
try {
JSONObject obj = new JSONObject(response.a().g()).getJSONObject("response");
JSONObject obj = new JSONObject(GzipDecompressor.decompressResponse(response)).getJSONObject("response");
SparseArray<MiniUser> users = new SparseArray<>();
parseUsers(users, obj.optJSONArray("profiles"), false);
@@ -1,9 +1,11 @@
package ru.vtosters.lite.foaf;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.util.Log;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.network.Network;
import com.vtosters.lite.R;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -11,8 +13,11 @@ import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.proxy.api.VikaMobile;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.time.LocalDate;
@@ -24,8 +29,7 @@ import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.proxy.ProxyUtils.isAnyProxyEnabled;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
public class FoafBase {
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
@@ -36,13 +40,14 @@ public class FoafBase {
public static JSONObject getBypassedOnlineInfo(int id) throws JSONException {
Request request = new Request.a()
.b(API_VKNEXT + id)
.a("Accept-Encoding", "gzip")
.a();
JSONObject dummy = new JSONObject();
dummy.put("last_seen", 0);
try (Response response = client.a(request).execute()) {
JSONObject online_info = new JSONObject(response.a().g()).getJSONObject("response").optJSONObject(Integer.toString(id));
JSONObject online_info = new JSONObject(GzipDecompressor.decompressResponse(response)).getJSONObject("response").optJSONObject(Integer.toString(id));
return online_info == null ? dummy : online_info;
} catch (Exception e) {
Log.e("GetBypassedOnlineInfo", e.getMessage());
@@ -53,12 +58,13 @@ public class FoafBase {
public static JSONObject getBypassedOnlineInfo(String ids) {
Request request = new Request.a()
.b(API_VKNEXT + ids)
.a("Accept-Encoding", "gzip")
.a();
JSONObject dummy = new JSONObject();
try (Response response = client.a(request).execute()) {
JSONObject online_info = new JSONObject(response.a().g()).getJSONObject("response").optJSONObject("response");
JSONObject online_info = new JSONObject(GzipDecompressor.decompressResponse(response)).getJSONObject("response").optJSONObject("response");
return online_info == null ? dummy : online_info;
} catch (Exception e) {
Log.e("GetBypassedOnlineInfo1", e.getMessage());
@@ -66,9 +72,9 @@ public class FoafBase {
}
}
@SuppressLint("DefaultLocale")
private static String getLink(int i) {
String prefix = isAnyProxyEnabled() ? "https://" + getApi() + "/_/vk.com/foaf.php?id=" : "https://vk.com/foaf.php?id=";
return prefix + i;
return ProxyUtils.isAnyProxyEnabled() ? String.format("https://%s/_/vk.com/foaf.php?id=%d", ProxyUtils.getApi(), i) : String.format("https://vk.com/foaf.php?id=%d", i);
}
public static void loadAndShow(Context context, int i) {
@@ -79,12 +85,13 @@ public class FoafBase {
Request request = new Request.a()
.b(getLink(i))
.a("Accept-Encoding", "gzip")
.a();
try (Response response = client.a(request).execute()) {
try (Response response = Network.b(CLIENT_API).a(request).execute()) {
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> {
try {
showFoafInfo(progressDialog, context, i, response.a().g());
showFoafInfo(progressDialog, context, i, GzipDecompressor.decompressResponse(response));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -120,6 +127,7 @@ public class FoafBase {
}
}
@SuppressLint("DefaultLocale")
private static void showFoafDialog(Context context, int i, String normalHumanDate, long daysPassed) {
String message = String.format("%s %d%s %s%s %d", context.getString(R.string.foafid), i, context.getString(R.string.foafregdate), normalHumanDate, context.getString(R.string.foafdate), daysPassed);
new VkAlertDialog.Builder(context)
@@ -2,30 +2,35 @@ package ru.vtosters.lite.music;
import android.text.TextUtils;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.net.URLEncoder;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
public class Genius {
private static final String KEY = "Bearer ZTejoT_ojOEasIkT9WrMBhBQOz6eYKK5QULCMECmOhvwqjRZ6WbpamFe3geHnvp3";
private static final String URL = "https://api.genius.com";
private static final OkHttpClient client = VtOkHttpClient.getInstance();
public static String getTextMusic(MusicTrack musictrack) {
var uid = musictrack.y1();
var artist = removeExtras(musictrack.C);
var title = removeExtras(musictrack.f);
var duration = musictrack.h;
String uid = musictrack.y1();
String artist = removeExtras(MusicTrackUtils.getArtists(musictrack));
String title = removeExtras(musictrack.f);
int duration = musictrack.h;
if (TextUtils.isEmpty(uid) || TextUtils.isEmpty(artist) || TextUtils.isEmpty(title) || duration == 0) {
Log.d("Genius", "grabTrackInfo: " + "Empty track, info: " + artist + " - " + title + " - " + duration + " - " + uid);
@@ -42,17 +47,18 @@ public class Genius {
}
Request request = new Request.a()
.b(URL + "/search/songs?q=" + URLEncoder.encode(artist + " " + track) + "&from_background=0" + "&page=1")
.a("User-Agent", "Genius/5.14.0 (Android; Android 13; Google Pixel 4 XL)")
.b(String.format("%s/search/songs?q=%s&from_background=0&page=1", URL, URLEncoder.encode(artist + " " + track)))
.a("User-Agent", "Genius/5.21.3 (Android; Android 13; Google Pixel 4 XL)")
.a("Authorization", KEY)
.a("x-genius-app-background-request", "0")
.a("x-genius-logged-out", "true")
.a("host", "api.genius.com")
.a("Content-Type", "application/x-www-form-urlencoded")
.a("Accept-Encoding", "gzip")
.b()
.a();
try (Response resp = client.a(request).execute()) {
String payload = resp.a().g();
String payload = GzipDecompressor.decompressResponse(resp);
JSONArray sections = new JSONObject(payload).getJSONObject("response").getJSONArray("sections");
for (int i = 0; i < sections.length(); i++) {
@@ -86,17 +92,18 @@ public class Genius {
private static String getText(String track) {
Request request = new Request.a()
.b(URL + track + "?text_format=plain")
.a("User-Agent", "Genius/5.14.0 (Android; Android 13; Google Pixel 4 XL)")
.b(String.format("%s%s?text_format=plain", URL, track))
.a("User-Agent", "Genius/5.21.3 (Android; Android 13; Google Pixel 4 XL)")
.a("Authorization", KEY)
.a("x-genius-app-background-request", "0")
.a("x-genius-logged-out", "true")
.a("host", "api.genius.com")
.a("Content-Type", "application/x-www-form-urlencoded")
.a("Accept-Encoding", "gzip")
.b()
.a();
try {
JSONObject payload = new JSONObject(client.a(request).execute().a().g());
try (Response resp = client.a(request).execute()) {
JSONObject payload = new JSONObject(GzipDecompressor.decompressResponse(resp));
if (!payload.has("response")) {
return AndroidUtils.getString("error_no_text");
@@ -107,4 +114,4 @@ public class Genius {
return AndroidUtils.getString("error_no_text") + "\n\n" + AndroidUtils.getString(R.string.error) + ": \n" + e.getMessage();
}
}
}
}
@@ -3,18 +3,14 @@ package ru.vtosters.lite.music;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import com.vtosters.lite.R;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -23,6 +19,20 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
public class LastFMScrobbler {
private static final String KEY = "5965d63402414776c54c266db0211746";
private static final String KEY_SECRET = "d982180eed13275bb948e41cf225f88f";
@@ -32,7 +42,7 @@ public class LastFMScrobbler {
public static void grabMusicTrack(MusicTrack musictrack) {
var uid = musictrack.y1();
var artist = musictrack.C;
var artist = MusicTrackUtils.getArtists(musictrack);
var title = musictrack.f;
var duration = musictrack.h;
var track_id = musictrack.d;
@@ -83,12 +93,12 @@ public class LastFMScrobbler {
params.put("sk", getSessionKey());
params.put("artist[0]", artist);
params.put("track[0]", title);
if (!TextUtils.isEmpty(album))
if (!TextUtils.isEmpty(album)) {
params.put("album[0]", album);
}
params.put("duration[0]", String.valueOf(duration));
params.put("timestamp[0]", String.valueOf(System.currentTimeMillis() / 1000));
fetch(params, new Callback() {
@Override
public void a(Call call, IOException e) {
@@ -150,8 +160,9 @@ public class LastFMScrobbler {
params.put("format", "json");
var reqBodyBuilder = new FormBody.a(StandardCharsets.UTF_8);
for (var entry : params.entrySet())
for (var entry : params.entrySet()) {
reqBodyBuilder.a(entry.getKey(), entry.getValue());
}
var req = new Request.a()
.b(URL)
@@ -164,6 +175,7 @@ public class LastFMScrobbler {
public static void fetchSession(JSONObject json) throws JSONException {
if (!json.has("session")) {
Log.d("Scrobbler", "Auth failed - no session");
AndroidUtils.sendToast("Ошибка входа");
return;
}
@@ -177,6 +189,8 @@ public class LastFMScrobbler {
.putBoolean("lastfm_enabled", true)
.apply();
AndroidUtils.sendToast("Успешный вход");
Log.d("Scrobbler", "Auth success as " + name + ", key " + key);
}
@@ -2,14 +2,21 @@ package ru.vtosters.lite.music.cache;
import android.os.RemoteException;
import android.text.TextUtils;
import com.vk.dto.music.MusicTrack;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.util.List;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
public class MusicCacheImpl {
public static void addTrack(MusicTrack track) {
@@ -19,7 +26,7 @@ public class MusicCacheImpl {
track.I != null ? track.I.getId() + "" : "-1",
track.f,
!TextUtils.isEmpty(track.g) ? track.g : "",
track.C,
MusicTrackUtils.getArtists(track),
track.I != null ? track.I.getTitle() : "",
track.K,
track.h,
@@ -31,20 +38,28 @@ public class MusicCacheImpl {
MusicCacheStorageUtils.removeTrackDirById(trackId);
}
public static List<MusicTrack> getAllTracks() {
return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext());
public static List<MusicTrack> getAllOwnTracks() {
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1");
}
public static List<MusicTrack> getAlbumById(String albumId) {
return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(), albumId);
public static List<MusicTrack> getPlaylistSongs(String owner_id, String playlist_id) {
return PlaylistCacheDbDelegate.getTracksInPlaylist(AndroidUtils.getGlobalContext(), owner_id + "_" + playlist_id); // get songs from playlist
}
public static List<MusicTrack> getFirstAlbumTrack(String albumId) {
return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(), albumId);
public static Playlist getPlaylist(String playlist_id, String owner_id) {
return getPlaylistById(owner_id + "_" + playlist_id); // get playlist from db
}
public static List<MusicTrack> getPlaylist() {
return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext());
public static List<Playlist> getPlaylists() {
return PlaylistCacheDbDelegate.getAllPlaylists(AndroidUtils.getGlobalContext()); // get all cached playlists
}
public static Playlist getPlaylistById(String query) {
return PlaylistCacheDbDelegate.getPlaylistById(AndroidUtils.getGlobalContext(), query); // get playlist from db
}
public static boolean hasPlaylist() {
return PlaylistCacheDbDelegate.getPlaylistsCount(AndroidUtils.getGlobalContext()) != 0; // has playlists or not
}
public static long getTracksCount() {
@@ -57,7 +72,7 @@ public class MusicCacheImpl {
try {
return (long) service.getCache().size();
} catch (RemoteException e) {
e.printStackTrace();
e.fillInStackTrace();
return defaultValue();
}
}
@@ -79,6 +94,7 @@ public class MusicCacheImpl {
public static void clear() {
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
PlaylistCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
MusicCacheStorageUtils.clear();
}
}
}
@@ -8,13 +8,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.Nullable;
import com.vk.dto.music.AlbumLink;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Thumb;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
import java.lang.annotation.Retention;
@@ -23,6 +24,12 @@ import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
/**
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
@@ -116,12 +123,14 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
vals.put(Constants.COLUMN_TITLE, track.f);
vals.put(Constants.COLUMN_SUBTITLE, track.g);
vals.put(Constants.COLUMN_ARTIST, track.C);
vals.put(Constants.COLUMN_ARTIST, MusicTrackUtils.getArtists(track));
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
vals.put(Constants.COLUMN_DURATION, track.h);
vals.put(Constants.COLUMN_HAS_ARTWORK, track.I != null && track.I.u1() != null);
db.insert(Constants.TABLE_NAME, null, vals);
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), AccountManagerUtils.getUserId() + "_-1", track.y1());
}
}
}
@@ -165,18 +174,21 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
}
public MusicTrack getTrackById(String trackId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_TRACK_ID + " = ?", new String[]{trackId}, null, null, null);
MusicTrack track = null;
if (cursor.moveToFirst()) {
track = fromCursor(cursor);
}
cursor.close();
return track;
}
public List<MusicTrack> getAllTracks() {
return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null));
}
public List<MusicTrack> getAlbum(String albumId) {
return getAlbumTracks(albumId, null);
}
public List<MusicTrack> getFirstAlbumTrack(String albumId) {
return getAlbumTracks(albumId, "0,1");
}
private List<MusicTrack> getAlbumTracks(String albumId, @Nullable String limit) {
return getTracksWithCursor(getReadableDatabase().query(
Constants.TABLE_NAME,
@@ -215,7 +227,7 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
@Retention(RetentionPolicy.SOURCE)
public @interface Constants {
int DV_VERSION = 0x2;
int DV_VERSION = 0x3;
String DB_NAME = "vt_lite_cache.db";
String TABLE_NAME = "tracks";
//columns
@@ -0,0 +1,254 @@
package ru.vtosters.lite.music.cache.db;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.utils.music.MusicCacheStorageUtils.*;
public class PlaylistCacheDb extends SQLiteOpenHelper implements AutoCloseable {
public PlaylistCacheDb(Context context) {
super(context, Constants.DB_NAME, null, Constants.DV_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Constants.CREATE_QUERY);
db.execSQL(Constants.CREATE_PLAYLIST_TRACKS_QUERY);
}
private static Playlist fromCursor(Cursor cur) throws JSONException {
@SuppressLint("Range")
String photoString = cur.getString(cur.getColumnIndex(Constants.COLUMN_PHOTO));
JSONObject photo = null;
try {
photo = new JSONObject(photoString);
} catch (JSONException e) {
// ok and
}
@SuppressLint("Range")
JSONObject playlist = PlaylistHelper.generatePlaylist(
cur.getInt(cur.getColumnIndex(Constants.COLUMN_ID)),
cur.getInt(cur.getColumnIndex(Constants.COLUMN_OWNER_ID)),
Boolean.parseBoolean(cur.getString(cur.getColumnIndex(Constants.COLUMN_IS_EXPLICIT))),
cur.getString(cur.getColumnIndex(Constants.COLUMN_TITLE)),
cur.getString(cur.getColumnIndex(Constants.COLUMN_DESCRIPTION)),
photo,
0);
Log.d("Playlist", "generated " + Playlist.U.a(playlist).v1());
return Playlist.U.a(playlist);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(Constants.DROP_QUERY);
onCreate(db);
}
public void addPlaylist(int id, int ownerId, boolean isExplicit, String title, String description, JSONObject photo) {
ContentValues values = new ContentValues();
values.put(Constants.COLUMN_ID, id);
values.put(Constants.COLUMN_OWNER_ID, ownerId);
values.put(Constants.COLUMN_IS_EXPLICIT, isExplicit);
values.put(Constants.COLUMN_TITLE, title);
values.put(Constants.COLUMN_DESCRIPTION, description);
values.put(Constants.COLUMN_PHOTO, String.valueOf(photo));
getWritableDatabase().insert(Constants.TABLE_NAME, null, values);
}
private void deletePlaylist(int id, int ownerId) {
getWritableDatabase().delete(Constants.TABLE_NAME, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)});
}
public boolean isCachedPlaylist(int id, int ownerId) {
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, new String[]{Constants.COLUMN_ID}, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}
public long getPlaylistsCount() {
return DatabaseUtils.queryNumEntries(getReadableDatabase(), Constants.TABLE_NAME);
}
public void deletePlaylistWithTracks(String playlistId) {
SQLiteDatabase db = getWritableDatabase();
String[] parts = playlistId.split("_");
int id = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
// Get all tracks in the playlist
List<MusicTrack> tracks = getTracksInPlaylist(playlistId);
// Delete the playlist
deletePlaylist(id, ownerId);
// Delete each track from the playlist_tracks table
for (MusicTrack track : tracks) {
String trackId = track.y1();
removeTrackFromPlaylist(playlistId, trackId);
removePlaylistThumb(playlistId);
if (!isTrackInAnyOtherPlaylist(trackId, playlistId)) {
MusicCacheImpl.removeTrack(trackId);
Log.d("Playlist", "removed " + trackId);
}
}
// Delete all entries from the playlist_tracks table for this playlist
db.delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ?", new String[]{playlistId});
}
private boolean isTrackInAnyOtherPlaylist(String trackId, String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(
Constants.TABLE_PLAYLIST_TRACKS,
null,
Constants.COLUMN_TRACK_ID + " = ? AND " + Constants.COLUMN_PLAYLIST_ID + " != ?",
new String[]{trackId, playlistId},
null, null, null
);
boolean exists = cursor.getCount() > 0;
cursor.close();
return exists;
}
public List<Playlist> getAllPlaylists() {
Cursor cursor = getReadableDatabase().query(Constants.TABLE_NAME, null, null, null, null, null, null);
List<Playlist> playlists = getPlaylistsWithCursor(cursor);
cursor.close();
return playlists;
}
public Playlist getPlaylistById(int id, int ownerId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(Constants.TABLE_NAME, null, Constants.COLUMN_ID + " = ? AND " + Constants.COLUMN_OWNER_ID + " = ?", new String[]{String.valueOf(id), String.valueOf(ownerId)}, null, null, null);
Playlist playlist = null;
if (cursor.moveToFirst()) {
try {
playlist = fromCursor(cursor);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
cursor.close();
return playlist;
}
private List<Playlist> getPlaylistsWithCursor(Cursor cur) {
List<Playlist> playlists = new ArrayList<>();
while (cur.moveToNext()) {
try {
playlists.add(fromCursor(cur));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
return playlists;
}
public void addTrackToPlaylist(String playlistId, String trackId) {
ContentValues values = new ContentValues();
values.put(Constants.COLUMN_PLAYLIST_ID, playlistId);
values.put(Constants.COLUMN_TRACK_ID, trackId);
getWritableDatabase().insert(Constants.TABLE_PLAYLIST_TRACKS, null, values);
}
public void removeTrackFromPlaylist(String playlistId, String trackId) {
getWritableDatabase().delete(Constants.TABLE_PLAYLIST_TRACKS, Constants.COLUMN_PLAYLIST_ID + " = ? AND " + Constants.COLUMN_TRACK_ID + " = ?", new String[]{playlistId, trackId});
}
public List<MusicTrack> getTracksInPlaylist(String playlistId) {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(
Constants.TABLE_PLAYLIST_TRACKS,
new String[]{Constants.COLUMN_TRACK_ID},
Constants.COLUMN_PLAYLIST_ID + " = ?",
new String[]{playlistId},
null, null, null
);
List<MusicTrack> tracks = new ArrayList<>();
while (cursor.moveToNext()) {
@SuppressLint("Range")
String trackId = cursor.getString(cursor.getColumnIndex(Constants.COLUMN_TRACK_ID));
MusicTrack track = TracklistHelper.getTrack(trackId);
Log.d("Playlist", "add " + trackId + " to playlist " + playlistId);
if (track != null) {
tracks.add(track);
}
}
cursor.close();
return tracks;
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
@Retention(RetentionPolicy.SOURCE)
public @interface Constants {
int DV_VERSION = 0x2;
String DB_NAME = "vt_lite_cache_playlists.db";
String TABLE_NAME = "playlists";
//columns
String COLUMN_ID = "id";
String COLUMN_OWNER_ID = "owner_id";
String COLUMN_IS_EXPLICIT = "is_explicit";
String COLUMN_TITLE = "title";
String COLUMN_DESCRIPTION = "description";
String COLUMN_PHOTO = "photo";
// tracks
String COLUMN_TRACK_ID = "track_id";
String TABLE_PLAYLIST_TRACKS = "playlist_tracks";
String COLUMN_PLAYLIST_ID = "playlist_id";
//queries
String CREATE_QUERY = "create table " + TABLE_NAME
+ "("
+ COLUMN_ID + " INTEGER PRIMARY KEY autoincrement,"
+ COLUMN_OWNER_ID + " INTEGER not null,"
+ COLUMN_IS_EXPLICIT + " TEXT not null,"
+ COLUMN_TITLE + " TEXT not null,"
+ COLUMN_DESCRIPTION + " TEXT not null,"
+ COLUMN_PHOTO + " TEXT not null"
+ ")";
String CREATE_PLAYLIST_TRACKS_QUERY = "create table " + TABLE_PLAYLIST_TRACKS + "("
+ COLUMN_PLAYLIST_ID + " TEXT not null,"
+ COLUMN_TRACK_ID + " TEXT not null"
+ ")";
String DROP_QUERY = "drop table if exists " + TABLE_NAME;
}
}
@@ -1,16 +1,16 @@
package ru.vtosters.lite.music.cache;
package ru.vtosters.lite.music.cache.delegate;
import android.content.Context;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
import java.util.List;
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
public class MusicCacheDbDelegate {
static synchronized private MusicCacheDb connectToDb(Context context) {
synchronized (MusicCacheDb.class) {
return new MusicCacheDb(context);
}
private static MusicCacheDb connectToDb(Context context) {
return new MusicCacheDb(context);
}
public static void addTrack(Context context,
@@ -43,27 +43,9 @@ public class MusicCacheDbDelegate {
}
}
public static List<MusicTrack> getAllTracks(Context context) {
public static MusicTrack getTrackById(Context context, String trackId) {
try (var db = connectToDb(context)) {
return db.getAllTracks();
}
}
public static List<MusicTrack> getAlbumById(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getAlbum(albumId);
}
}
public static List<MusicTrack> getFirstAlbumTrack(Context context, String albumId) {
try (var db = connectToDb(context)) {
return db.getFirstAlbumTrack(albumId);
}
}
public static List<MusicTrack> getPlaylist(Context context) {
try (var db = connectToDb(context)) {
return db.getPlaylist();
return db.getTrackById(trackId);
}
}
@@ -0,0 +1,118 @@
package ru.vtosters.lite.music.cache.delegate;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.db.PlaylistCacheDb;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.util.List;
public class PlaylistCacheDbDelegate {
static synchronized private PlaylistCacheDb connectToDb(Context context) {
synchronized (PlaylistCacheDb.class) {
return new PlaylistCacheDb(context);
}
}
public static void addPlaylist(Context context, Playlist playlist) {
try (var db = connectToDb(context)) {
db.addPlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, generatePhotoJSON(playlist));
}
}
public static JSONObject generatePhotoJSON(Playlist playlist) {
if (PlaylistUtils.getThumb(playlist) != null) {
JSONObject photo = new JSONObject();
String playlistId = playlist.v1();
try {
photo.put("height", 300);
photo.put("width", 300);
addPhotoSizeToJSON(photo, playlistId, 600, "photo_600");
addPhotoSizeToJSON(photo, playlistId, 34, "photo_34");
addPhotoSizeToJSON(photo, playlistId, 1200, "photo_1200");
addPhotoSizeToJSON(photo, playlistId, 68, "photo_68");
addPhotoSizeToJSON(photo, playlistId, 135, "photo_135");
addPhotoSizeToJSON(photo, playlistId, 300, "photo_300");
addPhotoSizeToJSON(photo, playlistId, 270, "photo_270");
return photo;
} catch (JSONException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
private static void addPhotoSizeToJSON(JSONObject photo, String playlistId, int size, String key) throws JSONException {
photo.put(key, Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
Log.d("Playlist", "thumb link " + Uri.fromFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, size)).toString());
}
public static void deletePlaylist(Context context, String playlistId) {
try (var db = connectToDb(context)) {
db.deletePlaylistWithTracks(playlistId);
}
}
public static boolean isCachedPlaylist(Context context, String id) {
try (var db = connectToDb(context)) {
String[] parts = id.split("_");
int playlistId = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
return db.isCachedPlaylist(playlistId, ownerId);
}
}
public static long getPlaylistsCount(Context context) {
try (var db = connectToDb(context)) {
return db.getPlaylistsCount();
}
}
public static List<Playlist> getAllPlaylists(Context context) {
try (var db = connectToDb(context)) {
return db.getAllPlaylists();
}
}
public static Playlist getPlaylistById(Context context, String playlistId) {
try (var db = connectToDb(context)) {
String[] parts = playlistId.split("_");
int id = Integer.parseInt(parts[1]);
int ownerId = Integer.parseInt(parts[0]);
return db.getPlaylistById(id, ownerId);
}
}
public static void addTrackToPlaylist(Context context, String playlistId, String trackId) {
try (var db = connectToDb(context)) {
db.addTrackToPlaylist(playlistId, trackId);
}
}
public static void removeTrackFromPlaylist(Context context, String playlistId, String trackId) {
try (var db = connectToDb(context)) {
db.removeTrackFromPlaylist(playlistId, trackId);
}
}
public static List<MusicTrack> getTracksInPlaylist(Context context, String playlistId) {
try (var db = connectToDb(context)) {
return db.getTracksInPlaylist(playlistId);
}
}
public static void drop(Context context) {
context.deleteDatabase(PlaylistCacheDb.Constants.DB_NAME);
}
}
@@ -1,92 +1,82 @@
package ru.vtosters.lite.music.cache.helpers;
import android.net.Uri;
import android.util.Log;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
public class PlaylistHelper {
public static Playlist createCachedPlaylistMetadata() {
try {
return new Playlist(getPlaylist());
return new Playlist(getCachedSongsPlaylist());
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public static Playlist createCachedPlaylistMetadata(String id) {
var track = TracklistHelper.getTracksOne(id).get(0);
var albumLink = track.I;
var thumb = albumLink.u1();
return createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
track.C,
track.y1(),
albumLink.getId()
);
}
public static Playlist createAlbum(String photo, String title, String artist, String trackId, int albumId) {
public static JSONArray addCachedPlaylists(JSONArray jsonArray, Boolean noPlaylists) {
try {
var jPhoto = new JSONObject();
if (!photo.isEmpty())
jPhoto.put("height", 600)
.put("width", 600)
.putOpt("photo_300", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 300)).toString())
.putOpt("photo_600", Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId, 600)).toString());
var obj = new JSONObject()
.put("id", -2)
.put("owner_id", albumId)
.put("type", 1)
.put("album_type", "main_only")
.put("title", title)
.put("description", albumId + "/ " + AndroidUtils.getString(R.string.cached_album_warning))
.put("main_artists", new JSONArray()
.put(new JSONObject().put("name", artist)
.put("id", "-1")
.put("domain", "-1")))
.putOpt("photo", jPhoto)
.put("count", 0);
return new Playlist(obj);
if (noPlaylists) {
jsonArray.put(new JSONArray().put(getCachedSongsPlaylist()));
} else {
jsonArray.put(getCachedSongsPlaylist());
}
for (Playlist playlist : MusicCacheImpl.getPlaylists()) {
if (noPlaylists) {
jsonArray.put(new JSONArray().put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O)));
} else {
jsonArray.put(generatePlaylist(playlist.a, playlist.b, playlist.C, playlist.g, playlist.B, PlaylistUtils.getThumb(playlist), playlist.O));
}
Log.d("PlaylistHelper", "Playlist cache added: " + playlist.a + " " + playlist.b + " " + playlist.C + " " + playlist.g + " " + playlist.B);
}
} catch (JSONException e) {
e.printStackTrace();
return null;
e.fillInStackTrace();
}
return jsonArray;
}
public static List<Playlist> getAlbumPlaylists() {
List<Playlist> list = new ArrayList<>();
for (var track : MusicCacheImpl.getPlaylist()) {
var albumLink = track.I;
var thumb = albumLink.u1();
list.add(createAlbum(
thumb != null ? thumb.h(600) : "",
albumLink.getTitle(),
track.C,
track.y1(),
albumLink.getId()
));
public static JSONObject generatePlaylist(int id, int owner_id, boolean is_explicit, String title, String description, JSONObject photo, int count) throws JSONException {
JSONObject playlist = new JSONObject()
.put("id", id)
.put("owner_id", owner_id)
.put("type", 0)
.put("album_type", "playlist")
.put("is_explicit", is_explicit)
.put("title", title)
.put("description", description)
.put("access_key", "cache")
.put("genres", new JSONArray())
.put("is_following", false)
.put("followers", 0)
.put("plays", 0)
.put("create_time", 0)
.put("update_time", 0)
.put("subtitle", "")
.put("meta", new JSONObject()
.put("view", "compact"))
.put("count", count);
if (photo != null) {
playlist.put("photo", photo);
}
return list;
return playlist;
}
public static JSONObject getPlaylist() throws JSONException {
public static JSONObject getCachedSongsPlaylist() throws JSONException {
return new JSONObject()
.put("id", -1)
.put("owner_id", getUserId())
.put("owner_id", AccountManagerUtils.getUserId())
.put("type", 0)
.put("album_type", "playlist")
.put("title", AndroidUtils.getString(R.string.cached_tracks_title))
@@ -111,7 +101,21 @@ public class PlaylistHelper {
.put("height", 600))
.put("meta", new JSONObject()
.put("view", "compact"))
.put("count", 0);
.put("count", MusicCacheImpl.getAllOwnTracks().size());
}
public static JSONArray getCachedPlaylistsIds() {
JSONArray arr = new JSONArray();
if (NetworkUtils.isNetworkConnected() && !MusicCacheImpl.getAllOwnTracks().isEmpty()) {
arr.put(AccountManagerUtils.getUserId() + "_-1");
}
for (var playlist : MusicCacheImpl.getPlaylists()) {
arr.put(playlist.v1());
}
return arr;
}
public static JSONObject getCatalogPlaylist() throws JSONException {
@@ -121,18 +125,17 @@ public class PlaylistHelper {
.put("layout", new JSONObject()
.put("name", "large_slider")
.put("is_editable", 0)
.put("owner_id", getUserId()))
.put("playlists_ids", new JSONArray()
.put(getUserId() + "_-1"));
.put("owner_id", AccountManagerUtils.getUserId()))
.put("playlists_ids", getCachedPlaylistsIds());
}
public static JSONObject getCatalogHeader() throws JSONException {
public static JSONObject getCatalogHeader(String text) throws JSONException {
return new JSONObject()
.put("id", "cache")
.put("data_type", "none")
.put("layout", new JSONObject()
.put("name", "header")
.put("title", AndroidUtils.getString(R.string.cached_tracks_title)));
.put("title", text));
}
public static JSONObject getCatalogSeparator() throws JSONException {
@@ -142,4 +145,4 @@ public class PlaylistHelper {
.put("layout", new JSONObject()
.put("name", "separator"));
}
}
}
@@ -8,6 +8,8 @@ import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.MusicCacheDbDelegate;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
@@ -22,7 +24,7 @@ public class TracklistHelper {
}
public static List<MusicTrack> getTracks() {
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
List<MusicTrack> tracks = MusicCacheImpl.getAllOwnTracks();
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
@@ -33,13 +35,8 @@ public class TracklistHelper {
return tracks;
}
public static List<MusicTrack> getTracks(String id) {
return MusicCacheImpl.getAlbumById(id);
}
public static List<MusicTrack> getTracksOne(String id) {
return MusicCacheImpl.getFirstAlbumTrack(id);
public static MusicTrack getTrack(String id) {
return MusicCacheDbDelegate.getTrackById(AndroidUtils.getGlobalContext(), id);
}
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
@@ -1,9 +1,12 @@
package ru.vtosters.lite.music.callback;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.vtosters.lite.R;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.utils.AndroidUtils;
@@ -40,12 +43,12 @@ public class MusicCallbackBuilder {
} catch (UnsatisfiedLinkError e) {
Log.e("AudioDownloader", "native libs");
Log.e("AudioDownloader", e.getMessage());
onFailure();
onFailure(e);
}
}
@Override
public void onFailure() {
public void onFailure(Throwable e) {
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@@ -74,10 +77,11 @@ public class MusicCallbackBuilder {
public void onSuccess() {
notification.setContentText(AndroidUtils.getString(R.string.player_download_finished));
notification.setProgress(0, 0, false);
notification.setSmallIcon(android.R.drawable.stat_sys_download_done);
}
@Override
public void onFailure() {
public void onFailure(Throwable e) {
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
notificationManager.notify(notificationId, notification.build());
}
@@ -6,12 +6,13 @@ import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.ArrayList;
@@ -19,6 +20,7 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.DateHook.getLocale;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
@@ -50,17 +52,19 @@ public class PlaylistConverter {
var request = new Request.a()
.b(requestUrl)
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
try {
var response = CompletableFuture.supplyAsync(() -> {
try {
return new OkHttpClient().a(request).execute().a().g();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
).get();
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
return GzipDecompressor.decompressResponse(resp);
} catch (IOException e) {
throw new RuntimeException(e);
}
}).get();
var jsonObj = new JSONObject(response);
if (!jsonObj.has("response"))
return null;
@@ -1,45 +0,0 @@
package ru.vtosters.lite.music.converter.ts;
import com.arthenica.ffmpegkit.FFmpegKit;
import com.vk.dto.music.Artist;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.downloader.M3UDownloader;
import ru.vtosters.lite.utils.IOUtils;
import java.io.File;
import java.util.stream.Collectors;
public class FFMpeg {
/**
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
* @param out Output .mp3 file
* @param track Track to be converted
* @return True if success, else false
*/
public static void convert(File in, String out, MusicTrack track) {
// convert .ts file to .mp3 file via ffmpeg
var cmd = " -y -i \"" + in.getAbsolutePath() + '"'
+ " -map 0 -dn"
+ " -loglevel error"
+ " -hide_banner"
+ " -write_id3v2 1"
+ setMetadata(track)
+ " -c copy \"" + out + '"';
var ses = FFmpegKit.execute(cmd);
if (ses.getReturnCode().isValueCancel()) throw new RuntimeException(ses.getLogsAsString());
}
private static String setMetadata(MusicTrack track) {
return Preferences.getBoolValue("setMetaData", true) ? " -metadata title=\"" + IOUtils.getValidFileName(M3UDownloader.getTitle(track)) + '"'
+ (track.L == null || track.I == null
? " -metadata artist=\"" + normalizeMetadata(track.C) + '"'
: " -metadata artist=\"" + normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))) + '"'
+ " -metadata album=\"" + normalizeMetadata(track.I.getTitle()) + '"') : "";
}
private static String normalizeMetadata(String in) {
return in.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -0,0 +1,79 @@
package ru.vtosters.lite.music.converter.ts;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class MpegDemuxer {
public static void convert(byte[] inBytes, String out) throws IOException {
try (InputStream inStream = new ByteArrayInputStream(inBytes)) {
convert(inStream, out);
}
}
public static void convert(InputStream in, String out) throws IOException {
int packetSize = 188;
int desiredPid = 256;
var buffer = new byte[packetSize];
var packet = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
try (var fis = new BufferedInputStream(in); var fos = new BufferedOutputStream(new FileOutputStream(out))) {
while (fis.read(buffer) != -1) {
packet.clear();
int firstByte = packet.get() & 0xFF;
int secondByte = packet.get() & 0xFF;
int thirdByte = packet.get() & 0xFF;
int fourthByte = packet.get() & 0xFF;
int syncByte = firstByte;
int transportErrorIndicator = (secondByte & 0x80) >> 7;
int payloadUnitStartIndicator = (secondByte & 0x40) >> 6;
int pid = ((secondByte & 0x1F) << 8) | (thirdByte & 0xFF);
int adaptationFieldControl = (fourthByte & 0x30) >> 4;
if (syncByte != 0x47) {
Log.d("MpegDemuxer", "Invalid sync byte: 0x" + Integer.toHexString(syncByte));
continue;
}
if (transportErrorIndicator == 1) {
Log.d("MpegDemuxer", "Transport error indicator is set");
continue;
}
if (adaptationFieldControl == 2 || adaptationFieldControl == 3) {
int adaptationFieldLength = packet.get() & 0xFF;
packet.position(packet.position() + adaptationFieldLength);
}
if (pid == desiredPid) {
if (payloadUnitStartIndicator == 1) {
packet.position(packet.position() + 4);
int pesPacketLength = packet.getShort() & 0xFFFF;
if (pesPacketLength != 0) {
packet.position(packet.position() + 2);
int pesHeaderDataLength = packet.get() & 0xFF;
packet.position(packet.position() + pesHeaderDataLength);
} else {
Log.v("MpegDemuxer", "PES packet length is zero, that shouldn't happen in audio stream");
}
}
fos.write(packet.array(), packet.position(), packetSize - packet.position());
}
}
}
}
}
@@ -1,70 +0,0 @@
package ru.vtosters.lite.music.converter.ts;
import android.os.Build;
import ru.vtosters.lite.utils.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Comparator;
public class TSMerger {
/**
* Merge multiple .ts files into one .ts file
*
* @param in Directory with .ts files
*/
public static void merge(File in, File out) {
File[] tsesList = in.listFiles();
if (tsesList == null) {
return;
}
ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
sortTsesList(tsesList);
for (File file : tsesList) {
if (file.getName().endsWith(".ts")) {
try {
mergedByteArrayStream.write(IOUtils.readFully(file));
} catch (IOException e) {
return;
}
}
}
writeOutputFile(out, mergedByteArrayStream);
}
private static void sortTsesList(File[] tsesList) {
Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
}
private static int getFileIndex(File file) {
String fileName = file.getName();
int dotIndex = fileName.indexOf('.', 1);
return Integer.parseInt(fileName.substring(0, dotIndex));
}
private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
byte[] bytes = mergedByteArrayStream.toByteArray();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.write(out.toPath(), bytes);
} catch (IOException ignored) {
// ignored
}
} else {
try (FileOutputStream fos = new FileOutputStream(out)) {
fos.write(bytes);
} catch (IOException ignored) {
// ignored
}
}
}
}
@@ -6,11 +6,12 @@ import com.vk.core.util.DeviceIdProvider;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.ArrayList;
@@ -18,6 +19,7 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.DateHook.getLocale;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
@@ -44,12 +46,14 @@ public class AudioGet {
var request = new Request.a()
.b(requestUrl)
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
.a("Accept-Encoding", "gzip")
.a("User-Agent", Network.l.c().a())
.a("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.a();
try {
var response = CompletableFuture.supplyAsync(() -> {
try {
return new OkHttpClient().a(request).execute().a().g();
try (Response resp = Network.b(CLIENT_API).a(request).execute()) {
return GzipDecompressor.decompressResponse(resp);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -0,0 +1,65 @@
package ru.vtosters.lite.music.downloader;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
public final class CachedDownloader implements ITrackDownloader {
private final ITrackDownloader origin;
public CachedDownloader(ITrackDownloader origin) {
this.origin = origin;
}
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
origin.download(track, new Callback() {
@Override
public void onProgress(int progress) {
callback.onProgress(progress);
}
@Override
public void onSuccess() {
new ThumbnailTrackDownloader().download(track, new Callback() {
@Override public void onProgress(int progress) {
}
@Override public void onSuccess() {
}
@Override
public void onFailure(Throwable e) {
callback.onFailure(e);
}
@Override public void onSizeReceived(long size, long header) {
}
}, null);
PlaylistCacheDbDelegate.addTrackToPlaylist(AndroidUtils.getGlobalContext(), playlist.v1(), track.y1());
MusicCacheImpl.addTrack(track);
callback.onSuccess();
}
@Override
public void onFailure(Throwable e) {
callback.onFailure(e);
}
@Override
public void onSizeReceived(long size, long header) {
callback.onSizeReceived(size, header);
}
}, playlist);
}
}
@@ -0,0 +1,56 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.mpatric.mp3agic.ID3v23Tag;
import com.mpatric.mp3agic.InvalidDataException;
import com.mpatric.mp3agic.Mp3File;
import com.mpatric.mp3agic.NotSupportedException;
import com.mpatric.mp3agic.UnsupportedTagException;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Artist;
import java.io.File;
import java.io.IOException;
import java.util.stream.Collectors;
import ru.vtosters.hooks.other.Preferences;
public class ID3Tagger {
public static void tag(File file, MusicTrack track) {
try {
// https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
// TODO: get rid of file duplication
var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
if (!file.renameTo(oldFile)) {
Log.e("ID3Tagger", "Failed to rename file");
return;
}
var mp3File = new Mp3File(oldFile);
if (mp3File.hasId3v2Tag()) {
var id3v2Tag = new ID3v23Tag();
if (track.L == null || track.I == null) {
id3v2Tag.setArtist(track.C);
} else {
var artists = track.L.stream().map(Artist::w1).collect(Collectors.joining(Preferences.metadataSeparator()));
id3v2Tag.setArtist(artists);
id3v2Tag.setAlbum(track.I.getTitle());
}
id3v2Tag.setTitle(Mp3Downloader.getTitle(track));
mp3File.setId3v2Tag(id3v2Tag);
mp3File.save(file.getAbsolutePath());
if (!oldFile.delete()) {
Log.e("ID3Tagger", "Failed to delete file");
}
}
} catch (IOException | UnsupportedTagException | InvalidDataException |
NotSupportedException e) {
Log.e("ID3Tagger", "Failed to tag file", e);
}
}
}
@@ -1,181 +0,0 @@
package ru.vtosters.lite.music.downloader;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import java8.util.concurrent.CompletionException;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.converter.ts.FFMpeg;
import ru.vtosters.lite.music.converter.ts.TSMerger;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class M3UDownloader
implements ITrackDownloader {
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
static void parse(
File outDir,
Callback callback,
MusicTrack track,
boolean cache)
throws IOException {
var parser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
var segments = ((f) parser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
var progress = new AtomicInteger();
callback.onProgress(5);
var resultTs = getResultTsFile(track);
var resultMp3 = getResultMp3File(outDir, cache, track);
try {
CompletableFuture.allOf(createFutures(baseUri, segments, progress, callback, track))
.thenRun(() -> {
if (cache) {
try {
ThumbnailDownloader.downloadThumbnails(track);
} catch (IOException e) {
throw new RuntimeException("Failed to download thumbs", e);
}
}
})
.thenRun(() -> {
try {
TSMerger.merge(getTsesDir(track), resultTs);
} catch (Throwable e) {
throw new RuntimeException("Failed to merge ts files", e);
}
})
.thenRun(() -> {
try {
FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
} catch (Throwable e) {
throw new RuntimeException("FFmpeg error", e);
}
})
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
.thenRun(() -> {
if (cache) {
MusicCacheImpl.addTrack(track);
}
})
.whenComplete((unused, tr) -> {
if (tr != null) {
IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1()));
callback.onFailure();
} else {
callback.onSuccess();
}
IOUtils.deleteRecursive(getTsesDir(track));
})
.join();
} catch (CompletionException e) {
Log.e("M3UDownloader", "Failed to download track", e.getCause());
}
}
@SuppressWarnings("rawtypes")
static CompletableFuture[] createFutures(
String baseUri,
List<a> segments,
AtomicInteger progress,
Callback callback,
MusicTrack track) {
var futures = new CompletableFuture[segments.size()];
for (var i = 0; i < segments.size(); ++i) {
var segment = segments.get(i);
var filename = i + ".ts";
futures[i] = CompletableFuture.runAsync(() -> processSegment(baseUri, segment, filename, progress, segments.size(), callback, track));
}
return futures;
}
private static void processSegment(
String baseUri,
a segment,
String filename,
AtomicInteger progress,
int size,
Callback callback,
MusicTrack track) {
try {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
//if IV doesn't pass, it must be created manually and filled with zeros
if (cipherBytes.length != 16) Arrays.fill((cipherBytes = new byte[16]), (byte) 0x0);
var cipherIv = new IvParameterSpec(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
buff = cipher.doFinal(buff);
}
IOUtils.writeToFile(new File(getTsesDir(track), filename), buff);
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? +'(' + track.g + ')' : "");
}
static File getTsesDir(MusicTrack track) {
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
var tsesDir = new File(filesDir, String.valueOf(track.d));
tsesDir.mkdirs();
return tsesDir;
}
static File getResultTsFile(MusicTrack track) {
return new File(getTsesDir(track), "result.ts");
}
static File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
return new File(outDir, IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3"));
}
@Override
public void downloadTrack(
MusicTrack track,
File outDir,
Callback callback,
boolean cache) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
try {
parse(outDir, callback, track, cache);
} catch (IOException e) {
callback.onFailure();
Log.e("M3UDownloader", e + "");
throw new RuntimeException(e);
}
}
// Initialization-on-demand
private static class Holder {
private static final M3UDownloader INSTANCE = new M3UDownloader();
}
}
@@ -1,76 +0,0 @@
//package ru.vtosters.lite.music;
//
//import android.text.TextUtils;
//import android.util.Log;
//import android.widget.Toast;
//
//import com.vk.dto.music.MusicTrack;
//
//import org.json.JSONException;
//import org.json.JSONObject;
//
//import java.io.File;
//import java.io.IOException;
//
//import okhttp3.Call;
//import okhttp3.Callback;
//import okhttp3.OkHttpClient;
//import okhttp3.Request;
//import okhttp3.Response;
//import ru.vtosters.lite.downloaders.AudioDownloader;
//import ru.vtosters.lite.music.interfaces.ITrackDownloader;
//import ru.vtosters.lite.utils.AndroidUtils;
//
//public class MP3Downloader implements ITrackDownloader {
// private static final String URL_FORMAT = "https://api.vk.com/method/audio.getById?v=5.131&access_token=%s&audios=%s&copyright=";
//
// private static final OkHttpClient client = new OkHttpClient();
//
// public static void downloadMP3(MusicTrack track) {
// var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
// if (token.isEmpty()) {
// Toast.makeText(AndroidUtils.getGlobalContext(), "Обновите токен VK Admin в настройках", Toast.LENGTH_SHORT).show();
// return;
// }
//
// var request = new Request.a()
// .b(String.format(URL_FORMAT, token, track.y1()))
// .a();
// client.a(request).a(new Callback() {
// @Override
// public void a(Call call, IOException e) {
// Log.d("MP3Downloader", e + "");
// }
//
// @Override
// public void a(Call call, Response response) throws IOException {
// var payload = response.a().g();
// Log.d("MP3Downloader", payload);
// try {
// JSONObject jsonObj = new JSONObject(payload);
// if (!jsonObj.has("response"))
// return;
// JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
// .getJSONObject(0);
// if (!responseNode.has("url"))
// return;
// var url = responseNode.optString("url");
// Log.d("MP3Downloader", url);
// if (TextUtils.isEmpty(url))
// return;
// if (VKM3UParser.isM3U8(url)) {
// AudioDownloader.downloadM3U8(track);
// return;
// }
// } catch (JSONException e) {
// Log.d("MP3Downloader", e + "");
// }
// }
// });
// }
//
// @Override
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
//
// }
//}
@@ -0,0 +1,98 @@
package ru.vtosters.lite.music.downloader;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.vk.dto.music.Playlist;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
public final class Mp3Downloader implements ITrackDownloader {
private final File outputFile;
public Mp3Downloader(File outputFile) {
this.outputFile = outputFile;
}
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
String uri = track.D;
if (Objects.requireNonNull(uri).isEmpty()) {
String msg = "link error: " + track.y1() + ", title: " + Mp3Downloader.getTitle(track);
Log.d("Mp3Downloader", msg);
throw new RuntimeException(msg);
}
var tsParser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
try {
var segments = ((f) tsParser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
byte[] buff = getMergedTs(baseUri, segments, callback);
MpegDemuxer.convert(buff, outputFile.getAbsolutePath());
if (Preferences.getBoolValue("setMetaData", false)) {
ID3Tagger.tag(outputFile, track);
}
callback.onSuccess();
} catch (IOException | GeneralSecurityException e) {
callback.onFailure(e);
}
}
private static byte[] getMergedTs(String baseUri, List<a> segments, Callback callback) throws IOException, GeneralSecurityException {
byte[] total = new byte[0];
int size = segments.size();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
int progress = 0;
for (var segment : segments) {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
//if IV doesn't pass, it must be created manually
if (cipherBytes.length != 16) cipherBytes = new byte[16];
var cipherIv = new IvParameterSpec(cipherBytes);
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
buff = cipher.doFinal(buff);
}
// copy on write
final int len = total.length, n = buff.length;
byte[] newBytes = Arrays.copyOf(total, len + n);
System.arraycopy(buff, 0, newBytes, len, n);
total = newBytes;
callback.onProgress(10 + Math.round(80.0F * (++progress) / size));
}
return total;
}
static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? '(' + track.g + ')' : "");
}
}
@@ -1,63 +1,67 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.music.interfaces.Callback;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java8.util.concurrent.CompletableFuture;
public class PlaylistDownloader {
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
var outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
else Log.e("PlaylistDownloader", "Directory creation failed");
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
@Override
public void onProgress(int progress) {
}
@Override
public void onSuccess() {
callback.onProgress(downloadedTracks.incrementAndGet());
}
@Override
public void onFailure() {
callback.onFailure();
}
@Override
public void onSizeReceived(long size, long header) {
}
});
Callback delegate = new ProgressCallback(callback);
CompletableFuture.allOf(playlist
.stream()
.map(x -> {
Callback.CompletableFutureCallback d = new
Callback.CompletableFutureCallback(delegate);
TrackDownloader.downloadTrack(x, path, d);
return d;
})
.toArray(CompletableFuture[]::new));
}
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
var downloadedTracks = new AtomicInteger();
for (var musicTrack : playlist)
TrackDownloader.cacheTrack(musicTrack, new Callback() {
@Override
public void onProgress(int progress) {
}
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback, Playlist playlistId) {
Callback delegate = new ProgressCallback(callback);
CompletableFuture.allOf(playlist
.stream()
.map(x -> {
Callback.CompletableFutureCallback d = new Callback.CompletableFutureCallback(delegate);
TrackDownloader.cacheTrack(x, d, playlistId);
return d;
})
.toArray(CompletableFuture[]::new));
}
@Override
public void onSuccess() {
callback.onProgress(downloadedTracks.incrementAndGet());
}
public static final class ProgressCallback implements Callback {
private final AtomicInteger progress = new AtomicInteger();
private final Callback origin;
@Override
public void onFailure() {
callback.onFailure();
}
public ProgressCallback(Callback origin) {
this.origin = origin;
}
@Override
public void onSizeReceived(long size, long header) {
}
});
@Override public void onProgress(int progress) {}
@Override
public void onSuccess() {
origin.onProgress(progress.incrementAndGet());
}
@Override
public void onFailure(Throwable e) {
origin.onFailure(e);
}
@Override public void onSizeReceived(long size, long header) {}
}
}
@@ -1,43 +0,0 @@
package ru.vtosters.lite.music.downloader;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import org.json.JSONArray;
import org.json.JSONObject;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.IOException;
import java.net.URL;
public class ThumbnailDownloader {
public static void downloadThumbnails(MusicTrack track) throws IOException {
JSONObject json = track.J();
JSONObject album = json.optJSONObject("album");
if (album == null) return;
JSONObject thumb = album.optJSONObject("thumb");
if (thumb == null) return;
JSONArray sizes = thumb.optJSONArray("sizes");
String trackId = LibVKXClient.asId(track);
if (sizes == null) {
JSONArray names = thumb.names();
if (names == null) return;
for (int i = 0; i < names.length(); ++i) {
String name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
}
} else {
for (int i = 0; i < sizes.length(); ++i) {
JSONObject size = sizes.optJSONObject(i);
int width = size.optInt("width");
String src = size.optString("src");
downloadThumbnail(src, width, trackId);
}
}
}
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -0,0 +1,57 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.PlaylistUtils;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class ThumbnailPlaylistDownloader implements ITrackDownloader {
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
JSONObject json = PlaylistUtils.getThumb(playlist); // get thumbnail photo
if (json == null) {
callback.onFailure(new IOException("No thumbnail found"));
return;
}
try {
List<String> sizes = List.of("600", "1200", "68", "135", "300", "270"); // define the order of sizes to download
JSONArray sizesArray = json.getJSONArray("sizes");
for (String size : sizes) {
for (int i = 0; i < sizesArray.length(); i++) {
JSONObject sizeObj = sizesArray.getJSONObject(i);
if (sizeObj.getInt("width") == Integer.parseInt(size)) {
String url = sizeObj.getString("src");
if (url.isEmpty()) continue;
Log.d("Playlist", "downloading thumb " + url);
downloadThumbnailPlaylist(url, Integer.parseInt(size), playlist.v1());
}
}
}
callback.onSuccess();
} catch (IOException | JSONException e) {
callback.onFailure(e);
throw new RuntimeException(e);
}
}
private static void downloadThumbnailPlaylist(String url, int res, String playlistId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getPlaylistThumb(playlistId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -0,0 +1,54 @@
package ru.vtosters.lite.music.downloader;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Playlist;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
public final class ThumbnailTrackDownloader implements ITrackDownloader {
@Override
public void download(MusicTrack track, Callback callback, Playlist playlist) {
JSONObject json = track.J();
JSONObject album = json.optJSONObject("album");
if (album == null) return;
JSONObject thumb = album.optJSONObject("thumb");
if (thumb == null) return;
JSONArray sizes = thumb.optJSONArray("sizes");
String trackId = LibVKXClient.asId(track);
try {
if (sizes == null) {
JSONArray names = thumb.names();
if (names == null) return;
for (int i = 0; i < names.length(); ++i) {
String name = names.optString(i);
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
}
} else {
for (int i = 0; i < sizes.length(); ++i) {
JSONObject size = sizes.optJSONObject(i);
int width = size.optInt("width");
String src = size.optString("src");
downloadThumbnail(src, width, trackId);
}
}
callback.onSuccess();
} catch (IOException e) {
callback.onFailure(e);
throw new RuntimeException(e);
}
}
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
}
}
@@ -1,51 +1,45 @@
package ru.vtosters.lite.music.downloader;
import android.util.Log;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.MusicTrack;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import java.io.File;
public class TrackDownloader {
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.dto.music.Playlist;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
import ru.vtosters.lite.utils.music.MusicTrackUtils;
public final class TrackDownloader {
private TrackDownloader() { }
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
download(track, path, callback, M3UDownloader.getInstance(), false);
}
public static void cacheTrack(MusicTrack track, Callback callback) {
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
callback.onSuccess();
return;
} else if (track.D.isEmpty()) {
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
callback.onFailure();
return;
}
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
download(track, path, callback, M3UDownloader.getInstance(), true);
}
private static void download(MusicTrack track, String path, Callback callback, ITrackDownloader downloader, boolean cache) {
File outDir = new File(path);
if (!outDir.exists())
if (outDir.mkdir())
if (!outDir.exists()) {
if (outDir.mkdir()) {
Log.v("TrackDownloader", "Directory created");
else
} else {
Log.e("TrackDownloader", "Directory creation failed");
}
}
File outputFile = new File(outDir, IOUtils.getValidFileName(MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3");
VTExecutors.getMusicDownloadExecutor().execute(
() -> downloader.downloadTrack(track, outDir, callback, cache)
);
VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback, null));
}
public static void cacheTrack(MusicTrack track, Callback callback, Playlist playlist) {
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
return;
}
File outputFile = MusicCacheStorageUtils.getTrackFile(LibVKXClient.asId(track));
VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader(new Mp3Downloader(outputFile)).download(track, callback, playlist));
}
}
@@ -1,11 +1,46 @@
package ru.vtosters.lite.music.interfaces;
import java8.util.concurrent.CompletableFuture;
public interface Callback {
void onProgress(int progress);
void onSuccess();
void onFailure();
void onFailure(Throwable e);
void onSizeReceived(long size, long header);
class CompletableFutureCallback
extends CompletableFuture<Void>
implements Callback {
private final Callback origin;
public CompletableFutureCallback(Callback origin) {
this.origin = origin;
}
@Override
public void onProgress(int progress) {
origin.onProgress(progress);
}
@Override
public void onSuccess() {
complete(null);
origin.onSuccess();
}
@Override
public void onFailure(Throwable e) {
completeExceptionally(e);
origin.onFailure(e);
}
@Override
public void onSizeReceived(long size, long header) {
origin.onSizeReceived(size, header);
}
}
}
@@ -1,8 +1,7 @@
package ru.vtosters.lite.music.interfaces;
import com.vk.dto.music.MusicTrack;
import java.io.File;
import com.vk.dto.music.Playlist;
/**
* Interface for track downloaders
@@ -12,8 +11,8 @@ public interface ITrackDownloader {
* Download track
*
* @param track Music track
* @param outDir Directory where to download the track
* @param callback Callback to call when download status changes
* @param playlist
*/
void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache);
void download(MusicTrack track, Callback callback, Playlist playlist);
}
@@ -1,38 +0,0 @@
package ru.vtosters.lite.proxy;
import android.util.Log;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
public class GithubList {
private static final OkHttpClient mClient = VtOkHttpClient.getInstance();
public static ArrayList<String> getProxy(String url, String proxyname) throws IOException {
Request link = new Request.a()
.b(url)
.a();
try {
var response = mClient.a(link).execute().a().g();
if (!response.contains(":")) {
Log.d(proxyname, "No proxy found");
return null;
}
Log.d(proxyname, response);
return new ArrayList<>(Arrays.asList(response.split("\n")));
} catch (Exception e) {
Log.d(proxyname, "Failed to fetch data, link: " + link);
return null;
}
}
}
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.util.ArrayMap;
import android.util.Log;
import com.vk.core.network.Network;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.http.CustomHttp;
import ru.vtosters.lite.proxy.https.CustomHttps;
@@ -62,16 +63,15 @@ public class ProxyUtils {
}
public static Boolean isAnyProxyEnabled() {
return isApiProxyEnabled() || isVikaProxyEnabled();
return (isApiProxyEnabled() || isVikaProxyEnabled()) && !isVKProxyEnabled();
}
public static Boolean isZaboronaEnabled() {
return Preferences.getString("proxy").equals("zaborona");
}
public static Boolean isRandomProxyEnabled() {
// return Preferences.getString("proxy").equals("randomproxy");
return false;
public static Boolean isVKProxyEnabled() {
return Network.l.b().d();
}
public static Boolean isApiProxyEnabled() {
@@ -125,15 +125,10 @@ public class ProxyUtils {
}
}
public static boolean hasProxy(String list) {
return list.contains(":"); // proxy format: ip:port
}
public static void setProxy() {
if (!isAnyProxyEnabled()) return;
if (!isAnyProxyEnabled() || Preferences.getString("proxy").equals("vika") || Preferences.getString("proxy").equals("apiproxy")) return;
switch (Preferences.getString("proxy")) {
case "zaborona" -> Zaborona.loadProxy();
// case "randomproxy" -> RandomProxy.loadProxy();
case "socks" -> CustomSocks.loadProxy();
case "http" -> CustomHttp.loadProxy();
case "https" -> CustomHttps.loadProxy();
@@ -154,4 +149,4 @@ public class ProxyUtils {
clearProperty("socksPortHost");
forceProxyApplying();
} // Reset all proxies
}
}
@@ -1,66 +0,0 @@
package ru.vtosters.lite.proxy;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.socks.Hookzof;
import ru.vtosters.lite.proxy.socks.Jetkai;
import ru.vtosters.lite.proxy.socks.Roosterkid;
import ru.vtosters.lite.proxy.socks.TheSpeedX;
import java.io.IOException;
import static ru.vtosters.lite.proxy.ProxyUtils.resetProxy;
public class RandomProxy {
public static String type = Preferences.getPreferences().getString("random_type", "none");
public static String host = Preferences.getPreferences().getString("random_host", "");
public static boolean showProxyResult = false;
public static void loadProxy() throws IOException {
if (host.isEmpty()) {
if (getProxy()) loadProxy();
return;
}
switch (type) {
case "socks" -> {
System.setProperty("socksProxyHost", host.split(":")[0]);
System.setProperty("socksProxyPort", host.split(":")[1]);
}
case "http" -> {
System.setProperty("http.proxyHost", host.split(":")[0]);
System.setProperty("http.proxyPort", host.split(":")[1]);
}
case "https" -> {
System.setProperty("https.proxyHost", host.split(":")[0]);
System.setProperty("https.proxyPort", host.split(":")[1]);
}
}
}
public static void setupNewProxy() throws IOException {
Preferences.getPreferences().edit().putString("random_type", "none").putString("random_host", "").commit();
resetProxy();
showProxyResult = true;
getProxy();
Preferences.getPreferences().edit().putString("proxy", "randomproxy").commit();
}
public static boolean getProxy() throws IOException {
var random = new java.util.Random();
var randomInt = random.nextInt(8);
switch (randomInt) {
case 0 -> Hookzof.loadProxy();
case 1 -> TheSpeedX.loadProxy();
case 2 -> Jetkai.loadProxy();
case 3 -> Roosterkid.loadProxy();
case 4 -> ru.vtosters.lite.proxy.http.Jetkai.loadProxy();
case 5 -> ru.vtosters.lite.proxy.http.TheSpeedX.loadProxy();
case 6 -> ru.vtosters.lite.proxy.https.Jetkai.loadProxy();
case 7 -> ru.vtosters.lite.proxy.https.Roosterkid.loadProxy();
}
return true;
}
}
@@ -1,48 +0,0 @@
package ru.vtosters.lite.proxy.http;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Jetkai {
private static final String TAG = "Jetkai_Http";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/txt/proxies-http.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "http").putString("random_host", proxy).commit();
System.setProperty("http.proxyHost", proxy.split(":")[0]);
System.setProperty("http.proxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.http;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class TheSpeedX {
private static final String TAG = "TheSpeedX_Http";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "http").putString("random_host", proxy).commit();
System.setProperty("http.proxyHost", proxy.split(":")[0]);
System.setProperty("http.proxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.https;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Jetkai {
private static final String TAG = "Jetkai_Https";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/txt/proxies-https.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "https").putString("random_host", proxy).commit();
System.setProperty("https.proxyHost", proxy.split(":")[0]);
System.setProperty("https.proxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,48 +0,0 @@
package ru.vtosters.lite.proxy.https;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Roosterkid {
private static final String TAG = "Roosterkid_Https";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTPS_RAW.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "https").putString("random_host", proxy).commit();
System.setProperty("https.proxyHost", proxy.split(":")[0]);
System.setProperty("https.proxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.socks;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Hookzof {
private static final String TAG = "Hookzof_Socks";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "socks").putString("random_host", proxy).commit();
System.setProperty("socksProxyHost", proxy.split(":")[0]);
System.setProperty("socksProxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.socks;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Jetkai {
private static final String TAG = "Jetkai_Socks";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/txt/proxies-socks5.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "socks").putString("random_host", proxy).commit();
System.setProperty("socksProxyHost", proxy.split(":")[0]);
System.setProperty("socksProxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.socks;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class Roosterkid {
private static final String TAG = "Roosterkid_Socks";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS5_RAW.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "socks").putString("random_host", proxy).commit();
System.setProperty("socksProxyHost", proxy.split(":")[0]);
System.setProperty("socksProxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -1,47 +0,0 @@
package ru.vtosters.lite.proxy.socks;
import android.util.Log;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.RandomProxy;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import static ru.vtosters.lite.proxy.GithubList.getProxy;
import static ru.vtosters.lite.proxy.ProxyUtils.forceProxyApplying;
import static ru.vtosters.lite.proxy.ProxyUtils.hasProxy;
public class TheSpeedX {
private static final String TAG = "TheSpeedX_Socks";
public static void loadProxy() throws IOException {
ArrayList<String> proxyList = getProxy("https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt", TAG);
if (proxyList == null) {
Log.d(TAG, "No proxy found");
return;
}
String proxy = proxyList.get(new java.util.Random().nextInt(proxyList.size()));
if (!hasProxy(proxy)) {
Log.d(TAG, "No proxy port found");
return;
}
Log.d(TAG, "Setting proxy: " + proxy.split(":")[0] + ":" + proxy.split(":")[1]);
if (RandomProxy.showProxyResult) {
AndroidUtils.sendToast(AndroidUtils.getString(R.string.proxy_setup_success) + " " + proxy);
RandomProxy.showProxyResult = false;
}
Preferences.getPreferences().edit().putString("random_type", "socks").putString("random_host", proxy).commit();
System.setProperty("socksProxyHost", proxy.split(":")[0]);
System.setProperty("socksProxyPort", proxy.split(":")[1]);
forceProxyApplying();
}
}
@@ -8,4 +8,4 @@ public class Zaborona {
System.setProperty("socksProxyPort", "1488");
forceProxyApplying();
}
}
}
@@ -13,6 +13,7 @@ import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.ArrayList;
@@ -32,6 +33,7 @@ public class UsersList {
Request request = new Request.a()
.b(Utils.getDomain() + "/api/getUsersWithServiceDescriptionsAndBanners")
.a("Accept-Encoding", "gzip")
.a();
VtOkHttpClient.getInstance().a(request).a(new Callback() {
@@ -43,7 +45,7 @@ public class UsersList {
@Override
public void a(Call call, Response response) {
try {
parseJson(new JSONObject(response.a().g()).getJSONObject("response"));
parseJson(new JSONObject(GzipDecompressor.decompressResponse(response)).getJSONObject("response"));
} catch (JSONException | IOException e) {
e.printStackTrace();
}
@@ -15,7 +15,7 @@ public class Utils {
"&vt_dark=" + (ThemesUtils.isDarkTheme() ? "1" : "0") +
"&vt_amoled=" + (ThemesUtils.isAmoledTheme() && ThemesUtils.isDarkTheme() ? "1" : "0") +
"&secret=" + AccountManagerUtils.getUserSecret() +
"&proxy=" + (ProxyUtils.isAnyProxyEnabled() ? "1" : "0") +
"&proxy=" + ((ProxyUtils.isAnyProxyEnabled() || ProxyUtils.isVKProxyEnabled()) ? "1" : "0") +
"&lang=" + DateHook.getLocale() +
"&vt=1" +
"&useragent=" + Base64Utils.encodeValue(Base64Utils.encode(Network.l.c().a())) +
@@ -28,8 +28,7 @@ public class ThemesCore {
private static int darken_color;
static {
setExceptions();
setThemedColors(ThemesUtils.getAccentColor());
init();
}
public static void setExceptions() {
@@ -201,4 +200,9 @@ public class ThemesCore {
public static boolean isCachedAccents() {
return cachedAccents;
}
}
public static void init() {
setExceptions();
setThemedColors(ThemesUtils.getAccentColor());
}
}
@@ -1,36 +1,36 @@
package ru.vtosters.lite.ui.dialogs;
import android.content.Context;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LayoutUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
public class RoundingSeekbarDialog {
private static final String[] roundOptions = {
"Круглые", "Закругленные", "Квадратные"
};
public static void dialog(Context context) {
LinearLayout mContainer = new LinearLayout(context);
SeekBar seek = new SeekBar(context);
seek.setMax(10);
seek.setKeyProgressIncrement(0);
seek.setProgress(Preferences.getPreferences().getInt("pic_rounding", 0));
seek.setPadding(AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f), AndroidUtils.dp2px(16f));
mContainer.addView(seek, LayoutUtils.createLinear(-1, -2));
new VkAlertDialog.Builder(context)
.setTitle(context.getString(R.string.pic_rounding_title))
.setMessage(context.getString(R.string.pic_rounding_info))
.setCancelable(true)
.setNegativeButton(context.getString(R.string.cancel), null)
.setView(mContainer)
.setPositiveButton(context.getString(R.string.vtl_confirm), (dialog, which) -> {
Preferences.getPreferences().edit().putInt("pic_rounding", seek.getProgress()).commit();
.setItems(roundOptions, (dialog, which) -> {
int val = getRoundingValue(which);
Preferences.getPreferences().edit().putInt("pic_rounding", val).apply();
LifecycleUtils.restartApplicationWithTimer();
})
.show();
}
}
private static int getRoundingValue(int selectedOption) {
int val = 0;
switch (selectedOption) {
case 1 -> val = 10;
case 2 -> val = 1;
}
return val;
}
}
@@ -5,6 +5,7 @@ import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.proxy.api.VikaMobile;
import ru.vtosters.lite.ui.fragments.VKUIwrapper;
import ru.vtosters.lite.utils.NavigatorUtils;
@@ -375,4 +375,4 @@ public class DataSettingsFragment extends TrackedMaterialPreferenceToolbarFragme
super.onResume();
updateCacheSize();
}
}
}
@@ -1,46 +1,35 @@
package ru.vtosters.lite.ui.fragments;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.preference.Preference;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.network.Network;
import com.vtosters.lite.R;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.downloaders.VideoDownloader;
import ru.vtosters.lite.music.LastFMScrobbler;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.SearchEngine;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
private static final ExecutorService executor = Executors.newCachedThreadPool();
public static void download(Context ctx) {
final EditText input = new EditText(ctx);
EditText input = new EditText(ctx);
input.setTextColor(ThemesUtils.getTextAttr());
input.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
@@ -64,26 +53,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.preferences_media);
prefs();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
@Override
public void onResume() {
super.onResume();
prefs();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
findPreference("maxquality").setEnabled(Preferences.isEnableExternalOpening());
return super.onPreferenceTreeClick(preference);
}
private void prefs() {
findPreference("download_video").setOnPreferenceClickListener(preference -> {
download(requireContext());
@@ -93,47 +63,6 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
deleteVideoHistoryDialog(requireContext());
return true;
});
findPreference("lastfm_auth").setOnPreferenceClickListener(preference -> {
if (LastFMScrobbler.isLoggedIn()) {
logout(getContext());
} else {
lastfmAuth(getContext());
}
return true;
});
if (LastFMScrobbler.isLoggedIn()) {
findPreference("lastfm_auth").setSummary(requireContext().getString(R.string.lastfm_authorized_as) + " " + LastFMScrobbler.getUserName());
} else {
findPreference("lastfm_enabled").setEnabled(false);
}
findPreference("lastfm").setVisible(!Preferences.serverFeaturesDisable());
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
if (MusicCacheImpl.isEmpty()) {
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
} else {
delcache(requireContext());
}
return true;
});
findPreference("useGenius").setVisible(!Preferences.serverFeaturesDisable());
findPreference("audio_download").setOnPreferenceClickListener(preference -> {
dlaudio(requireContext());
return true;
});
findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty());
findPreference("musicdefcatalog").setVisible(!Preferences.getBoolValue("useOldAppVer", false));
findPreference("useOldAppVer").setOnPreferenceClickListener(preference -> {
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("select_photo_search_engine").setOnPreferenceClickListener(preference -> {
var items = new ImagineArrayAdapter.ImagineArrayAdapterItem[SearchEngine.values().length + 1];
@@ -164,81 +93,10 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
findPreference("maxquality").setEnabled(Preferences.isEnableExternalOpening());
}
private void lastfmAuth(Context ctx) {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
final EditText fn = new EditText(ctx);
fn.setHint(R.string.lastfm_login);
fn.setTextColor(ThemesUtils.getTextAttr());
fn.setHintTextColor(ThemesUtils.getSTextAttr());
fn.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(fn);
fn.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) fn.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
fn.setLayoutParams(margin);
final EditText ln = new EditText(ctx);
ln.setHint(R.string.lastfm_password);
ln.setTextColor(ThemesUtils.getTextAttr());
ln.setHintTextColor(ThemesUtils.getSTextAttr());
ln.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(ln);
ln.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ln.setLayoutParams(margin);
new VkAlertDialog.Builder(ctx)
.setTitle(R.string.lastfm_enter_credentials)
.setPositiveButton(R.string.lastfm_enter,
(dialog, which) -> {
String login = fn.getText().toString();
String pass = ln.getText().toString();
LastFMScrobbler.authenticate(login, pass);
}
)
.setView(linearLayout)
.show();
}
private void logout(Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle(R.string.lastfm_logout_title)
.setMessage(R.string.lastfm_logout_confirm)
.setPositiveButton(R.string.vkim_yes,
(dialog, which) -> LastFMScrobbler.logout())
.setNeutralButton(R.string.vkim_no,
(dialog, which) -> dialog.cancel())
.show();
}
private void delcache(Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle(R.string.warning)
.setMessage(R.string.cached_tracks_remove_confirm)
.setPositiveButton(R.string.yes,
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
.setNeutralButton(R.string.no,
(dialog, which) -> dialog.cancel())
.show();
}
private void dlaudio(Context ctx) {
if (LibVKXClient.isIntegrationEnabled()) {
AndroidUtils.sendToast(AndroidUtils.getString("vkx_integration_enabled_info"));
return;
}
new VkAlertDialog.Builder(ctx)
.setTitle(R.string.download_method)
.setMessage(R.string.download_method_desc)
.setPositiveButton(R.string.download_method_cache, (dialog, which) -> {
executor.submit(AudioDownloader::cacheAllAudios);
})
.setNegativeButton(R.string.download_method_mp3, (dialog, which) -> {
executor.submit(AudioDownloader::downloadAllAudios);
})
.show();
@Override
public boolean onPreferenceTreeClick(Preference preference) {
findPreference("maxquality").setEnabled(Preferences.isEnableExternalOpening());
return super.onPreferenceTreeClick(preference);
}
public void deleteVideoHistory() {
@@ -249,7 +107,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
.a();
try {
var response = new JSONObject(new OkHttpClient().a(request).execute().a().g());
var response = new JSONObject(Network.b(CLIENT_API).a(request).execute().a().g());
if (response.optInt("response") == 1) {
requireActivity().runOnUiThread(() -> AndroidUtils.sendToast(requireContext().getString(R.string.video_history_cleaned)));
@@ -280,4 +138,4 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
public int T4() {
return R.string.vtlmedia;
}
}
}
@@ -0,0 +1,396 @@
package ru.vtosters.lite.ui.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.dto.music.Playlist;
import com.vtosters.lite.ui.SummaryListPreference;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.downloaders.AudioDownloader;
import ru.vtosters.lite.music.LastFMScrobbler;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.cache.delegate.PlaylistCacheDbDelegate;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
private static final ExecutorService executor = Executors.newCachedThreadPool();
@SuppressLint("DefaultLocale")
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(com.vtosters.lite.R.xml.empty);
requireActivity().getWindow().setStatusBarColor(ThemesUtils.getBackgroundContent());
requireActivity().getWindow().setNavigationBarColor(ThemesUtils.getBackgroundContent());
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Офлайн прослушивание");
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"cached_tracks",
getString(com.vtosters.lite.R.string.cached_tracks_title),
String.format(requireContext().getString(com.vtosters.lite.R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()),
null,
preference -> {
if (MusicCacheImpl.isEmpty()) {
AndroidUtils.sendToast(requireContext().getString(com.vtosters.lite.R.string.no_cache_error));
} else {
delcache(requireContext());
}
return true;
}
);
if (MusicCacheImpl.hasPlaylist()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"cached_playlists",
"Кешированные плейлисты",
String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()),
null,
preference -> {
cachedPlaylistsDialog(requireContext());
return true;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"audio_download",
getString(com.vtosters.lite.R.string.audio_download_title),
null,
null,
preference -> {
dlaudio(requireContext());
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"invertCachedTracks",
getString(com.vtosters.lite.R.string.invertcached_title),
getString(com.vtosters.lite.R.string.invertcached_summ),
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("invertCachedTracks", (boolean) o).apply();
return true;
}
).setEnabled(!MusicCacheImpl.isEmpty());
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"autocache",
getString(com.vtosters.lite.R.string.autocache_title),
getString(com.vtosters.lite.R.string.autocache_summ),
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("autocache", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Скачивание аудио в MP3");
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"dldir",
"Альтернативная папка для скачивания",
"Использовать папку Downloads вместо Music для скачивания музыки в MP3",
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("dldir", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"setMetaData",
"Сохранять метадату песен",
"Сохранять id3v2 теги для песен для MP3",
null,
true,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("setMetaData", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"metadataSeparator",
"Разделитель для id3v2 тегов",
null,
null,
preference -> {
LinearLayout linearLayout = new LinearLayout(requireContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
EditText separator = new EditText(requireContext());
separator.setHint("По-умолчанию: ; с пробелом");
separator.setTextColor(ThemesUtils.getTextAttr());
separator.setHintTextColor(ThemesUtils.getSTextAttr());
separator.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
separator.setText(Preferences.metadataSeparator());
linearLayout.addView(separator);
separator.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) separator.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
separator.setLayoutParams(margin);
new VkAlertDialog.Builder(requireContext())
.setTitle("Разделитель")
.setView(linearLayout)
.setPositiveButton("Сохранить", (dialog, which) -> {
if (separator.getText().toString().isEmpty()) {
AndroidUtils.sendToast("Разделитель не может быть пустым");
return;
}
Preferences.setMetadataSeparator(separator.getText().toString());
})
.setNegativeButton("Отмена", (dialog, which) -> dialog.cancel())
.show();
return true;
}
);
if (!Preferences.serverFeaturesDisable()) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция Genius");
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"useGenius",
getString(com.vtosters.lite.R.string.useGenius_title),
getString(com.vtosters.lite.R.string.useGenius_summ),
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("useGenius", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция Last.fm");
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"lastfm_auth",
getString(com.vtosters.lite.R.string.lastfm_auth_title),
getString(com.vtosters.lite.R.string.lastfm_auth_summ),
null,
preference -> {
if (LastFMScrobbler.isLoggedIn()) {
logout(getContext());
} else {
lastfmAuth(getContext());
}
updateLastFmPref();
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"lastfm_enabled",
AndroidUtils.getString("lastfm_enabled_title"),
AndroidUtils.getString("lastfm_enabled_summ"),
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("lastfm_enabled", (boolean) o).apply();
return true;
}
).setEnabled(LastFMScrobbler.isLoggedIn());
if (LastFMScrobbler.isLoggedIn()) {
findPreference("lastfm_auth").setSummary(getString(com.vtosters.lite.R.string.lastfm_authorized_as) + " " + LastFMScrobbler.getUserName());
}
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция VK X");
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
getString(com.vtosters.lite.R.string.vkx_why),
getString(com.vtosters.lite.R.string.vkx_why_summary),
null,
null
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"libvkx_integration",
getString(com.vtosters.lite.R.string.vkx_integration),
null,
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("libvkx_integration", (boolean) o).apply();
return true;
}
);
if (!LibVKXClient.isVkxInstalled()) {
findPreference("libvkx_integration").setSummary("У вас не установлен VKX. Интеграция работать не будет");
findPreference("libvkx_integration").setEnabled(false);
}
}
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Прочее");
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"useOldAppVer",
getString(com.vtosters.lite.R.string.useOldAppVer_title),
"Включите если вас не устраивают новые рекомендации и вас интересует только свое аудио",
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("useOldAppVer", (boolean) o).apply();
LifecycleUtils.restartApplicationWithTimer();
return true;
}
);
SummaryListPreference list = new SummaryListPreference(getPreferenceScreen().getContext());
list.setTitle(com.vtosters.lite.R.string.musicdefcatalog_title);
list.setKey("musicdefcatalog");
list.setEntryValues(com.vtosters.lite.R.array.musicdefcatalog);
list.setEntries(com.vtosters.lite.R.array.musicdefcatalog_value);
list.setDialogTitle(com.vtosters.lite.R.string.musicdefcatalog_dialogTitle);
list.setDefaultValue("default");
list.setVisible(!Preferences.getBoolValue("useOldAppVer", false));
getPreferenceScreen().addPreference(list);
}
private void lastfmAuth(Context ctx) {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
EditText fn = new EditText(ctx);
fn.setHint(com.vtosters.lite.R.string.lastfm_login);
fn.setTextColor(ThemesUtils.getTextAttr());
fn.setHintTextColor(ThemesUtils.getSTextAttr());
fn.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(fn);
fn.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) fn.getLayoutParams());
margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
fn.setLayoutParams(margin);
EditText ln = new EditText(ctx);
ln.setHint(com.vtosters.lite.R.string.lastfm_password);
ln.setTextColor(ThemesUtils.getTextAttr());
ln.setHintTextColor(ThemesUtils.getSTextAttr());
ln.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
linearLayout.addView(ln);
ln.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ln.setLayoutParams(margin);
new VkAlertDialog.Builder(ctx)
.setTitle(com.vtosters.lite.R.string.lastfm_enter_credentials)
.setPositiveButton(com.vtosters.lite.R.string.lastfm_enter,
(dialog, which) -> {
String login = fn.getText().toString();
String pass = ln.getText().toString();
LastFMScrobbler.authenticate(login, pass);
}
)
.setView(linearLayout)
.show();
}
public void updateLastFmPref() {
if (LastFMScrobbler.isLoggedIn()) {
findPreference("lastfm_auth").setSummary(getString(com.vtosters.lite.R.string.lastfm_authorized_as) + " " + LastFMScrobbler.getUserName());
findPreference("lastfm_enabled").setEnabled(true);
} else {
findPreference("lastfm_auth").setSummary(getString(com.vtosters.lite.R.string.lastfm_auth_summ));
findPreference("lastfm_enabled").setEnabled(false);
}
}
private void logout(Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle(com.vtosters.lite.R.string.lastfm_logout_title)
.setMessage(com.vtosters.lite.R.string.lastfm_logout_confirm)
.setPositiveButton(com.vtosters.lite.R.string.vkim_yes,
(dialog, which) -> LastFMScrobbler.logout())
.setNeutralButton(com.vtosters.lite.R.string.vkim_no,
(dialog, which) -> dialog.cancel())
.show();
}
private static void cachedPlaylistsDialog(Context ctx) {
List<Playlist> playlists = PlaylistCacheDbDelegate.getAllPlaylists(ctx);
String[] playlistNames = new String[playlists.size()];
for (int i = 0; i < playlists.size(); i++) {
playlistNames[i] = playlists.get(i).g; // Assuming Playlist class has a getTitle() method
}
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx);
builder.setTitle("Скачанные плейлисты");
builder.setItems(playlistNames, (dialog, which) -> {
Playlist selectedPlaylist = playlists.get(which);
String playlistId = selectedPlaylist.v1();
PlaylistCacheDbDelegate.deletePlaylist(ctx, playlistId);
AndroidUtils.sendToast("Плейлист удален");
});
builder.show();
}
private void delcache(Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle(com.vtosters.lite.R.string.warning)
.setMessage(com.vtosters.lite.R.string.cached_tracks_remove_confirm)
.setPositiveButton(com.vtosters.lite.R.string.yes,
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
.setNeutralButton(com.vtosters.lite.R.string.no,
(dialog, which) -> dialog.cancel())
.show();
}
private void dlaudio(Context ctx) {
if (LibVKXClient.isIntegrationEnabled()) {
AndroidUtils.sendToast(AndroidUtils.getString("vkx_integration_enabled_info"));
return;
}
new VkAlertDialog.Builder(ctx)
.setTitle(com.vtosters.lite.R.string.download_method)
.setMessage(com.vtosters.lite.R.string.download_method_desc)
.setPositiveButton(com.vtosters.lite.R.string.download_method_cache, (dialog, which) -> {
executor.submit(AudioDownloader::cacheAllAudios);
})
.setNegativeButton(com.vtosters.lite.R.string.download_method_mp3, (dialog, which) -> {
executor.submit(AudioDownloader::downloadAllAudios);
})
.show();
}
@Override
public int T4() {
return com.vtosters.lite.R.string.vtl_music_settings;
}
}
@@ -92,6 +92,11 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("batchmessages").setOnPreferenceChangeListener((preference, o) -> {
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("copyownlink").setOnPreferenceClickListener(preference -> {
copyText("https://vk.com/id" + AccountManagerUtils.getUserId());
ToastUtils.a(requireContext().getString(R.string.link_copied));
@@ -105,6 +110,7 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
findPreference("updateverifdata").setOnPreferenceClickListener(preference -> {
UsersList.getUsersList();
VTVerifications.isLoaded = false;
VTVerifications.load(requireContext());
AndroidUtils.sendToast(AndroidUtils.getString("data_updated"));
return true;
@@ -1,21 +1,32 @@
package ru.vtosters.lite.ui.fragments;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import b.h.g.k.VKProgressDialog;
import com.vk.core.network.Network;
import com.vk.core.network.proxy.NetworkProxy;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.NetworkProxyPreferences;
import com.vtosters.lite.R;
import com.vtosters.lite.ViewUtils;
public class ProxySettingsFragment extends TrackedMaterialPreferenceToolbarFragment {
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.preferences_proxy);
// findPreference("random_proxy").setOnPreferenceClickListener(preference -> {
// try {
// setupNewProxy();
// } catch (IOException e) {
// sendToast(requireContext().getString(R.string.get_proxy_error));
// }
// return true;
// });
initComponents();
}
private void initComponents() {
NetworkProxy networkProxy = Network.l.b();
boolean isEnabled = networkProxy.d();
SwitchPreference switchPreference = (SwitchPreference) findPreference("vkproxy");
switchPreference.setChecked(isEnabled);
switchPreference.setOnPreferenceChangeListener(new ProxyChangeListener(networkProxy, switchPreference));
}
@Override
@@ -23,3 +34,89 @@ public class ProxySettingsFragment extends TrackedMaterialPreferenceToolbarFragm
return R.string.vtlproxy;
}
}
class ProxyChangeListener implements Preference.OnPreferenceChangeListener {
private final NetworkProxy networkProxy;
private final SwitchPreference switchPreference;
ProxyChangeListener(NetworkProxy networkProxy, SwitchPreference switchPreference) {
this.networkProxy = networkProxy;
this.switchPreference = switchPreference;
}
@Override
public boolean onPreferenceChange(Preference preference, Object obj) {
handleProxyChange(preference, (Boolean) obj);
return false;
}
private void handleProxyChange(Preference preference, Boolean isEnabled) {
if (!networkProxy.d() && isEnabled) {
enableProxy(preference);
} else {
disableProxy();
}
}
private void enableProxy(Preference preference) {
networkProxy.a(true);
VKProgressDialog vKProgressDialog = createProgressDialog(preference.getContext());
networkProxy.a((NetworkProxy.c) new ProxyPreferences(vKProgressDialog));
}
private void disableProxy() {
Network.l.b().a(false);
switchPreference.setChecked(networkProxy.d());
}
private VKProgressDialog createProgressDialog(Context context) {
VKProgressDialog vKProgressDialog = new VKProgressDialog(context);
vKProgressDialog.setMessage(context.getString(R.string.loading));
vKProgressDialog.setOnDismissListener(new OnDismissListener());
ViewUtils.b(vKProgressDialog);
return vKProgressDialog;
}
private class OnDismissListener implements DialogInterface.OnDismissListener {
@Override
public void onDismiss(DialogInterface dialogInterface) {
switchPreference.setChecked(networkProxy.d());
}
}
private class ProxyPreferences extends NetworkProxyPreferences {
private final VKProgressDialog progressDialog;
ProxyPreferences(VKProgressDialog vKProgressDialog) {
this.progressDialog = vKProgressDialog;
}
@Override
public void b(NetworkProxy.Reason reason) {
super.b(reason);
dismissProgressDialog();
switchPreference.setChecked(networkProxy.d());
showToast(reason);
}
@Override
public void e() {
super.e();
dismissProgressDialog();
switchPreference.setChecked(networkProxy.d());
}
private void dismissProgressDialog() {
ViewUtils.a(progressDialog);
}
private void showToast(NetworkProxy.Reason reason) {
if (reason == NetworkProxy.Reason.PROXY_NOT_AVAILABLE) {
ToastUtils.a(R.string.sett_proxy_not_available);
} else {
ToastUtils.a(R.string.sett_no_proxy);
}
}
}
}
@@ -6,8 +6,10 @@ import android.os.Bundle;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.sponsorpost.data.Filter;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.services.FilterService;
import ru.vtosters.sponsorpost.utils.FiltersPreferences;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
@@ -35,6 +37,40 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
null
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Статистика блокировок");
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"SponsorPost",
"Заблокировано постов: " + PostsPreferences.getNumBlockedPosts(),
null,
null
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"SponsorPost фильтры",
"Заблокировано постов: " + FiltersPreferences.getNumBlockedPosts(),
null,
null
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Сброс статистики",
null,
null,
preference -> {
FiltersPreferences.dropNumBlockedPosts();
PostsPreferences.dropNumBlockedPosts();
AndroidUtils.sendToast("Статистика сброшена");
return false;
}
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Sponsor Post");
if (Preferences.serverFeaturesDisable()) {
@@ -49,9 +85,9 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
} else {
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"sponsorpost",
"",
"Фильтр постов",
"Получать списки рекламных постов в группах и ленте, которые не блокируются рекламными фильтрами",
"Получать списки рекламных постов в группах и ленте, которые не блокируются рекламными фильтрами\n\nРекомендуется использовать совместно со Стандартным фильтром",
null,
PostsPreferences.isEnabled(),
(preference, o) -> {
@@ -59,6 +95,61 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"",
"Не скрывать посты",
"Помечать посты рекламной пометкой, но не скрывать их полностью",
null,
PostsPreferences.isEnabledMarking(),
(preference, o) -> {
PostsPreferences.setEnabledMarking((boolean) o);
return true;
}
).setEnabled(PostsPreferences.isEnabled());
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"",
"Предварительные списки",
"Получать дополнительно более свежие списки которые ещё не попали в основной список. Нужно для активного голосования для блокировки постов. Возможно много ошибок!",
null,
VotesPreferences.isEnabled(),
(preference, o) -> {
VotesPreferences.setEnabled((boolean) o);
return true;
}
).setEnabled(PostsPreferences.isEnabled());
if (NetworkUtils.isNetworkConnected()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Обновить списки постов",
null,
null,
preference -> {
this.requireActivity().runOnUiThread(() -> {
Updates.updatePosts();
AndroidUtils.sendToast("Обновлено");
});
return false;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Телеграм чат",
"Помогите улучшить качество блокировки постов в нашем телеграм чате\n\nДля перехода в чат вам необходима последняя версия Telegram",
null,
preference -> {
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://t.me/vtosterschat/3559729")));
return false;
}
);
}
requireActivity().runOnUiThread(() -> {
@@ -123,6 +214,31 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
);
}
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"",
"Не блокировать посты",
"Помечать посты заблокированные фильтрами",
null,
FiltersPreferences.isEnabledMarking(),
(preference, o) -> {
FiltersPreferences.setEnabledMarking((boolean) o);
return true;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Телеграм чат",
"Помогите улучшить качество фильтров обсудив работу в нашем телеграм чате\n\nДля перехода в чат вам необходима последняя версия Telegram",
null,
preference -> {
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://t.me/vtosterschat/3559732")));
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -139,6 +255,20 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
}
}
);
if (Preferences.dev()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Сбросить токен пользователя",
null,
null,
preference -> {
VotesPreferences.remUserToken();
return false;
}
);
}
}
@Override
@@ -116,17 +116,6 @@ public class ThemesFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
var dockbarEditor = findPreference("dockbareditor");
dockbarEditor.setSummary(AndroidUtils.getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size());
dockbarEditor.setOnPreferenceClickListener(preference -> {
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
return true;
});
if (Preferences.vkme() || AndroidUtils.isTablet()) {
dockbarEditor.setVisible(false);
}
if (AndroidUtils.isTablet()) {
PreferenceCategory dockbarSettingsPreferenceCategory = (PreferenceCategory) findPreference("dockbarsett");
dockbarSettingsPreferenceCategory.setVisible(false);
@@ -42,9 +42,11 @@ import ru.vtosters.hooks.ui.SystemThemeChangerHook;
import ru.vtosters.lite.BuildConfig;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.deviceinfo.OEMDetector;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.ssfs.Utils;
import ru.vtosters.lite.themes.utils.RecolorUtils;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.ui.components.DockBarEditorManager;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
import ru.vtosters.lite.utils.*;
@@ -77,9 +79,13 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
public static String getProxysumm() {
String type = Preferences.getString("proxy");
boolean isVKProxy = ProxyUtils.isVKProxyEnabled();
if (type.equals("noproxy") || type.isEmpty())
if (isVKProxy) {
type = "Встроенный";
} else if (type.equals("noproxy") || type.isEmpty()) {
type = AndroidUtils.getString(R.string.vtlsettdisabled);
}
return AndroidUtils.getString(R.string.vtlproxysumm) + ": " + type;
}
@@ -466,6 +472,18 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.music),
getValAsString(R.string.useGenius_title, Preferences.getBoolValue("useGenius", false)),
R.drawable.ic_music_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), MusicFragment.class);
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -516,6 +534,20 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
}
);
if (!Preferences.vkme() && !AndroidUtils.isTablet()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.dockbar_editor),
requireContext().getString(R.string.vtldocksumm) + ": " + DockBarEditorManager.getInstance().getSelectedTabs().size(),
R.drawable.ic_pin_outline_28,
preference -> {
NavigatorUtils.switchFragment(requireContext(), DockBarEditorFragment.class);
return false;
}
);
}
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"currcache",
@@ -17,6 +17,8 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationManagerCompat;
import com.vk.core.util.AppContextHolder;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.general.fragments.WebViewFragment;
@@ -50,14 +52,8 @@ public class AndroidUtils {
@NonNull
public static Context getGlobalContext() {
try {
Method getInitialApplicationMtd = ReflectionUtils.findMethod(Class.forName("android.app.AppGlobals"), "getInitialApplication");
return (Context) getInitialApplicationMtd.invoke(null);
} catch (Exception e) {
Log.d("GlobalContext", "Error while fetching context via refl");
}
return LifecycleUtils.getCurrentActivity();
} // Getting the global context through reflection to use context on application initialization
return AppContextHolder.a;
} // Getting the global context to use context on application initialization
public static Resources getResources() {
return getGlobalContext().getResources();
@@ -8,6 +8,7 @@ import android.os.Build;
import android.text.TextUtils;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.dto.common.VideoFile;
import ru.vtosters.lite.downloaders.VideoDownloader;
import java.util.ArrayList;
import java.util.Arrays;
@@ -77,40 +78,7 @@ public class ExternalLinkParser {
* this.D = var66.optString("mp4_2160");
*/
public static boolean parseVKVideo(VideoFile videoFile, List<String> qualities, List<String> urls) {
if (!TextUtils.isEmpty(videoFile.D)) {
qualities.add("2160p");
urls.add(videoFile.D);
}
if (!TextUtils.isEmpty(videoFile.C)) {
qualities.add("1440p");
urls.add(videoFile.C);
}
if (!TextUtils.isEmpty(videoFile.B)) {
qualities.add("1080p");
urls.add(videoFile.B);
}
if (!TextUtils.isEmpty(videoFile.h)) {
qualities.add("720p");
urls.add(videoFile.h);
}
if (!TextUtils.isEmpty(videoFile.g)) {
qualities.add("480p");
urls.add(videoFile.g);
}
if (!TextUtils.isEmpty(videoFile.f)) {
qualities.add("360p");
urls.add(videoFile.f);
}
if (!TextUtils.isEmpty(videoFile.e)) {
qualities.add("240p");
urls.add(videoFile.e);
}
VideoDownloader.addVideos(videoFile, qualities, urls);
return qualities.size() > 0;
}
@@ -6,33 +6,48 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.vk.core.network.Network;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.hooks.other.ThemesUtils;
import java.net.URL;
import java.io.IOException;
public class ImageUtils {
// must be called asynchronously
public static Drawable getDrawableFromUrl(String url, int placeholderRes, boolean rounded, boolean scaled) {
if (NetworkUtils.isNetworkConnected() && !NetworkUtils.isInternetSlow()) {
try {
var bmp = BitmapFactory.decodeStream(new URL(url).openStream());
Bitmap bmp = downloadBitmap(url);
if (bmp != null) {
if (rounded) bmp = getBitmapClippedCircle(bmp);
if (scaled)
bmp = Bitmap.createScaledBitmap(bmp, AndroidUtils.dp2px(256), AndroidUtils.dp2px(256), true);
return new BitmapDrawable(AndroidUtils.getResources(), bmp);
if (bmp != null) {
if (rounded) {
bmp = getBitmapClippedCircle(bmp);
}
} catch (Exception e) {
e.printStackTrace();
if (scaled) {
bmp = Bitmap.createScaledBitmap(bmp, AndroidUtils.dp2px(256), AndroidUtils.dp2px(256), true);
}
return new BitmapDrawable(AndroidUtils.getResources(), bmp);
}
}
return placeholderRes > 0 ? ContextCompat.getDrawable(AndroidUtils.getGlobalContext(), placeholderRes) : null;
}
public static Bitmap downloadBitmap(String url) {
Request request = new Request.a()
.b(url)
.a();
try (Response resp = Network.b(Network.ClientType.CLIENT_IMAGE_LOADER).a(request).execute()) {
return BitmapFactory.decodeStream(resp.a().a());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) {
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
@@ -2,17 +2,19 @@ package ru.vtosters.lite.utils;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.vk.core.fragments.FragmentImpl;
import com.vk.extensions.NavigatorExt;
import com.vk.navigation.Navigator;
import com.vtosters.lite.TabletDialogActivity;
import ru.vtosters.lite.ui.fragments.VTSettings;
public class NavigatorUtils {
public static void switchFragment(Context context, Class<? extends FragmentImpl> fragmentClz) {
var intent = new Navigator(fragmentClz)
.b(context)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Navigator navigator = new Navigator(fragmentClz, new Bundle());
NavigatorExt.a(navigator, new TabletDialogActivity.b());
navigator.a(context);
}
public static void switchFragmentNavigator(Context context, Navigator fragmentClz) {
@@ -5,6 +5,7 @@ import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
@@ -25,6 +26,7 @@ public class OTAUtils {
public void loadData(boolean isManualCheck) {
Request release = new Request.a()
.b(LATEST_RELEASE_URL)
.a("Accept-Encoding", "gzip")
.a();
mClient.a(release).a(new Callback() {
@@ -46,12 +48,13 @@ public class OTAUtils {
});
}
void setData(Response response, boolean isManualCheck) throws IOException, JSONException {
mReleaseJson = new JSONObject(response.a().g());
void setData(Response response, boolean isManualCheck) throws JSONException, IOException {
mReleaseJson = new JSONObject(GzipDecompressor.decompressResponse(response));
String tag = mReleaseJson.getString("tag_name");
Request commit = new Request.a()
.b(String.format(LATEST_RELEASE_COMMIT_URL, tag))
.a("Accept-Encoding", "gzip")
.a();
mClient.a(commit).a(new Callback() {
@@ -62,15 +65,15 @@ public class OTAUtils {
}
@Override
public void a(Call call, Response response) throws IOException {
public void a(Call call, Response response) {
try {
mCommitJson = new JSONObject(response.a().g());
mCommitJson = new JSONObject(GzipDecompressor.decompressResponse(response));
mCommitSHA = mCommitJson.getJSONObject("object").getString("sha");
if (isNewVersion())
mListener.onUpdateApplied(isManualCheck);
else
mListener.onUpdateLatest(isManualCheck);
} catch (NullPointerException | JSONException e) {
} catch (NullPointerException | JSONException | IOException e) {
e.printStackTrace();
mListener.onUpdateError();
}
@@ -9,24 +9,24 @@ import static ru.vtosters.lite.foaf.FoafBase.getBypassedOnlineInfo;
public class OnlineBypass {
public static JSONObject setOnlineInfo(JSONObject json) throws JSONException {
var id = json.optInt("id");
int id = json.optInt("id");
if (id == AccountManagerUtils.getUserId()) {
return json;
}
var onlineinfo = json.optJSONObject("online_info");
JSONObject onlineinfo = json.optJSONObject("online_info");
if (onlineinfo != null && !onlineinfo.optBoolean("visible") && !Preferences.serverFeaturesDisable()) {
var bypassed = getBypassedOnlineInfo(id);
if (bypassed.optInt("last_seen", 0) != 0) {
json.remove("online_info");
var online_info = new JSONObject()
JSONObject online_info = new JSONObject()
.put("visible", true)
.put("last_seen", bypassed.optInt("last_seen"))
.put("is_online", bypassed.optBoolean("is_online"))
.put("app_id", bypassed.optInt("app_id"))
.put("is_mobile", bypassed.optBoolean("is_mobile"));
var last_seen = new JSONObject()
JSONObject last_seen = new JSONObject()
.put("platform", bypassed.optInt("platform"))
.put("time", bypassed.optInt("last_seen"));
@@ -37,51 +37,95 @@ public class OnlineBypass {
return json;
}
public static JSONArray setOnlineInfoUsers(JSONArray profiles) throws JSONException {
if (profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable()) return profiles;
public static JSONArray setOnlineInfoUsers(JSONArray profiles) {
if (shouldReturnProfiles(profiles)) return profiles;
StringBuilder sb = new StringBuilder();
var curVkId = AccountManagerUtils.getUserId();
int curVkId = AccountManagerUtils.getUserId();
for (int i = 0; i < profiles.length(); i++) {
JSONObject profile = profiles.getJSONObject(i);
JSONObject profile = getProfile(profiles, i);
int id = profile.optInt("id", -1);
JSONObject onlinfo = profile.optJSONObject("online_info");
if (id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible")) {
continue;
}
sb.append(id);
sb.append(",");
}
var ids = sb.toString();
if (!ids.isEmpty()) {
ids = ids.substring(0, ids.length() - 1);
} else {
return profiles;
if (shouldSkipProfile(id, curVkId, onlinfo)) continue;
sb.append(id).append(",");
}
String ids = prepareIds(sb);
if (ids.isEmpty()) return profiles;
JSONObject bypassedObj = getBypassedOnlineInfo(ids);
for (int i = 0; i < profiles.length(); i++) {
JSONObject profile = profiles.getJSONObject(i);
JSONObject profile = getProfile(profiles, i);
int id = profile.optInt("id");
JSONObject bypassed = bypassedObj.optJSONObject(Integer.toString(id));
if (bypassed == null) {
continue;
}
profile.remove("online_info");
var online_info = new JSONObject()
if (bypassed == null) continue;
JSONObject online_info = createOnlineInfo(bypassed);
JSONObject last_seen = createLastSeen(bypassed);
updateProfile(profile, online_info, last_seen);
}
return profiles;
}
private static boolean shouldReturnProfiles(JSONArray profiles) {
return profiles == null || profiles.length() == 0 || Preferences.serverFeaturesDisable();
}
private static JSONObject getProfile(JSONArray profiles, int i) {
try {
return profiles.getJSONObject(i);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static boolean shouldSkipProfile(int id, int curVkId, JSONObject onlinfo) {
return id == curVkId || id < 0 || onlinfo == null || onlinfo.optBoolean("visible");
}
private static String prepareIds(StringBuilder sb) {
String ids = sb.toString();
if (!ids.isEmpty()) {
ids = ids.substring(0, ids.length() - 1);
}
return ids;
}
private static JSONObject createOnlineInfo(JSONObject bypassed) {
try {
return new JSONObject()
.put("visible", true)
.put("last_seen", bypassed.optInt("last_seen"))
.put("is_online", bypassed.optBoolean("is_online"))
.put("app_id", bypassed.optInt("app_id"))
.put("is_mobile", bypassed.optBoolean("is_mobile"));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
var last_seen = new JSONObject()
private static JSONObject createLastSeen(JSONObject bypassed) {
try {
return new JSONObject()
.put("platform", bypassed.optInt("platform"))
.put("time", bypassed.optInt("last_seen"));
profile.put("last_seen", last_seen).put("online_info", online_info);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
return profiles;
private static void updateProfile(JSONObject profile, JSONObject online_info, JSONObject last_seen) {
try {
profile.remove("online_info");
profile.put("last_seen", last_seen).put("online_info", online_info);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@@ -3,6 +3,7 @@ package ru.vtosters.lite.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.vk.core.network.Network;
import com.vk.navigation.NavigatorKeys;
import okhttp3.*;
import org.json.JSONArray;
@@ -10,12 +11,11 @@ import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
@@ -44,13 +44,14 @@ public class VTVerifications {
Request request = new Request.a()
.b("https://vtosters.app/vktoaster/getGalo4kiBatch")
.a(RequestBody.a(MediaType.b("application/json; charset=UTF-8"), "{\"types\":[0,228,404]}"))
.a("Accept-Encoding", "gzip")
.a();
sClient.a(request).a(new Callback() {
@Override
public void a(Call call, Response response) {
try {
String payload = response.a().g();
String payload = GzipDecompressor.decompressResponse(response);
parseJson(payload);
prefs.edit()
.putString("ids", payload)
@@ -95,14 +96,15 @@ public class VTVerifications {
}
private static void processIds(JSONArray jsonIds, List<Integer> member) {
if (jsonIds == null || jsonIds.length() == 0)
if (jsonIds == null || jsonIds.length() == 0) {
return;
}
for (int i = 0; i < jsonIds.length(); i++) {
try {
member.add(jsonIds.getInt(i));
} catch (JSONException e) {
e.fillInStackTrace();
int id = jsonIds.optInt(i);
if (id != 0) {
member.add(id);
}
}
}
@@ -9,7 +9,6 @@ import static ru.vtosters.hooks.other.ThemesUtils.*;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class WebViewColoringUtils {
public static boolean isLoaded = false;
public static String loadedCSS = "";
public static String loadedCSSAmoled = "";
@@ -53,7 +52,6 @@ public class WebViewColoringUtils {
public static void load() {
loadedCSS = readCSS("vt_ui_accent.css");
loadedCSSAmoled = readCSS("vt_ui_oled.css");
isLoaded = true;
}
public static void inject(WebView webView, String str) {
@@ -11,32 +11,40 @@ public class MusicCacheStorageUtils {
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getTrackDirById(final String trackId) {
final var res = new File(getCacheStorageDir(), trackId);
public static File getTrackDirById(String trackId) {
File res = new File(getCacheStorageDir(), trackId);
res.mkdirs();
return res;
}
public static File getTrackFile(final String trackId) {
public static File getTrackFile(String trackId) {
return new File(getTrackDirById(trackId), "track.mp3");
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static File getThumbDirById(final String trackId) {
final var res = new File(getTrackDirById(trackId), "thumbs");
public static File getThumbDirById(String trackId) {
File res = new File(getTrackDirById(trackId), "thumbs");
res.mkdirs();
return res;
}
public static File getTrackThumb(final String trackId, final int factor) {
public static File getTrackThumb(String trackId, int factor) {
return new File(getThumbDirById(trackId), "thumb_" + factor + ".png");
}
public static void removeTrackDirById(final String trackId) {
public static File getPlaylistThumb(String playlistId, int factor) {
return new File(getThumbDirById(playlistId), "thumb_playlist_" + factor + ".png");
}
public static void removePlaylistThumb(String playlistId) {
IOUtils.deleteRecursive(getThumbDirById(playlistId));
}
public static void removeTrackDirById(String trackId) {
IOUtils.deleteRecursive(getTrackDirById(trackId));
}
public static void clear() {
IOUtils.deleteRecursive(getCacheStorageDir());
}
}
}

Some files were not shown because too many files have changed in this diff Show More