Compare commits

...
Author SHA1 Message Date
gdlbo adc5bb2b8c some more global renames 2024-03-29 15:16:33 +03:00
gdlbo 34f49a9b42 fixed rename in wall 2024-03-29 15:12:56 +03:00
gdlbo 84ad75e948 fixed disabler for playlist catalog 2024-03-29 13:45:28 +03:00
gdlbo e049b5f989 Update OEMDetector.java 2024-03-29 13:41:55 +03:00
Artur Skubei 5de52ed6b5 Rename Profiles/Groups v2 (#1596) 2024-03-29 13:38:30 +03:00
gdlbo 46c4f1c6dc hyperos support + gms refactored 2024-03-29 13:37:25 +03:00
gdlbo 126f5259d1 fixes 2024-03-29 10:47:18 +03:00
gdlbo 2d1867197f fixes of sponsorpost sdk
also added force update of filters
2024-03-28 22:20:07 +03:00
gdlbo c1d4216416 music stats catalog disabler 2024-03-28 20:16:39 +03:00
gdlbo 0f45a8d175 Update strings.xml 2024-03-28 20:10:08 +03:00
gdlbo 15ed01b4d3 fixed cache playlist shuffle 2024-03-28 20:05:10 +03:00
aliveoutside 41763a4b54 master.m3u8 fix 2024-03-26 22:59:32 +03:00
gdlbo d98d81f105 fixed autocache text dialog with dark theme 2024-03-26 17:47:10 +03:00
Artur Skubei 899554a571 Music autocaching v2 (#1591) 2024-03-26 17:36:34 +03:00
Artur Skubei 87b1a89bd8 Video metrics impl (#1590) 2024-03-25 23:45:42 +03:00
gdlbo c2b51338e8 Update MusicFragment.java 2024-03-24 22:55:58 +03:00
gdlbo 1d03182a1f fixed pause for music track stats 2024-03-24 22:40:01 +03:00
Artur Skubei bbad64c0aa Music metrics impl for recommendations (#1587) 2024-03-24 22:00:26 +03:00
Artur Skubei 53081115cc Fixed bypass online in profiles and messages (#1584) 2024-03-21 21:40:49 +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
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
157 changed files with 4213 additions and 6089 deletions
+2
View File
@@ -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);
}
}
@@ -0,0 +1,336 @@
package com.vk.libvideo;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.vk.bridges.StatisticsBridge1;
import com.vk.dto.common.VideoFile;
import com.vk.media.player.PlayerTypes;
import com.vk.navigation.NavigatorKeys;
import com.vk.statistic.Statistic;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.Metrics;
import java.util.Locale;
import java.util.Objects;
/* loaded from: classes3.dex */
public class VideoTracker {
private final VideoFile a;
/* renamed from: b reason: collision with root package name */
private Statistic f15638b;
/* renamed from: c reason: collision with root package name */
private String f15639c;
/* renamed from: d reason: collision with root package name */
private String f15640d;
/* renamed from: e reason: collision with root package name */
private final boolean f15641e;
/* renamed from: f reason: collision with root package name */
private int f15642f = -1;
private final int g;
private final int h;
private final int i;
private final int j;
private final int k;
private int l;
private PlayerType m;
private Screen n;
/* loaded from: classes3.dex */
public enum FullscreenTransition {
TAP,
SCREEN_ROTATION
}
/* loaded from: classes3.dex */
public enum PlayerType {
INLINE,
CAROUSEL,
FULLSCREEN
}
/* loaded from: classes3.dex */
public enum ResizeAction {
BUTTON,
GESTURE
}
/* loaded from: classes3.dex */
public enum RewindType {
SLIDER,
DOUBLE_TAP
}
/* loaded from: classes3.dex */
public enum Screen {
INLINE,
CAROUSEL,
PORTRAIT,
LANDSCAPE
}
public VideoTracker(VideoFile videoFile, @Nullable Statistic statistic, String str, boolean z, @Nullable String str2) {
this.a = videoFile;
this.f15638b = statistic;
int i = videoFile.d;
this.g = (int) (i * 0.25f);
this.h = (int) (i * 0.5f);
this.i = (int) (i * 0.75f);
this.j = (int) (i * 0.95f);
this.k = i - 1;
this.f15639c = str;
this.f15641e = z;
this.f15640d = str2;
}
private static boolean a(int i, int i2, int i3) {
return i3 > i && i3 <= i2;
}
public void a(String str) {
this.f15640d = str;
}
public boolean b() {
return this.f15638b != null;
}
public void c() {
this.f15642f = -1;
}
public void d() {
c("video_fullscreen_off");
}
public void e() {
c("video_fullscreen_on");
}
public void f() {
c("video_pause");
c("pause", "pause");
}
public void g() {
c("video_resume");
c("pause", "resume");
}
public void h() {
c("video_volume_off");
c("volume", "off");
}
public void i() {
c("video_volume_on");
c("volume", "on");
}
private void c(String str, String str2) {
a(b(str, str2));
}
public String a() {
return this.f15640d;
}
public void b(String str) {
if (Objects.equals(this.f15639c, str)) {
return;
}
this.f15639c = str;
}
private void c(String str) {
if (this.f15638b != null) {
StatisticsBridge1.a().a(this.f15638b, str);
}
}
public void a(@Nullable Statistic statistic) {
this.f15638b = statistic;
}
private Bundle b(String str, String str2) {
Bundle bundle = new Bundle();
bundle.putString(NotificationCompat.CATEGORY_EVENT, str);
bundle.putString("value", str2);
bundle.putInt("position", this.f15642f);
return bundle;
}
public void a(PlayerType playerType) {
if (this.m != playerType) {
this.m = playerType;
}
}
public void a(int i, int i2) {
int i3 = this.f15642f;
if (i == i3) {
return;
}
if (a(i3, i, 0)) {
a("start", i2);
c("video_start");
}
// if (this.a.d > 0) {
// if (a(this.f15642f, i, 3)) {
// a("3s", i2);
// c("video_play_3s");
// }
// if (a(this.f15642f, i, 10)) {
// a("10s", i2);
// c("video_play_10s");
// }
// if (a(this.f15642f, i, this.g)) {
// a("25", i2);
// c("video_play_25");
// }
// if (a(this.f15642f, i, this.h)) {
// a("50", i2);
// c("video_play_50");
// }
// if (a(this.f15642f, i, this.i)) {
// a("75", i2);
// c("video_play_75");
// }
// if (a(this.f15642f, i, this.j)) {
// a("95", i2);
// c("video_play_95");
// }
// if (a(this.f15642f, i, this.k)) {
// a("100", i2);
// c("video_play_100");
// }
// }
this.f15642f = i;
}
public void a(int i) {
a("continue", i);
c("video_start");
}
public void a(@NonNull Screen screen) {
if (this.n != screen) {
this.n = screen;
c("screen", screen.toString().toLowerCase());
}
}
public void a(@NonNull FullscreenTransition fullscreenTransition) {
c("full_screen_transition", fullscreenTransition.toString().toLowerCase());
}
public void a(int i, boolean z) {
if (this.l != i) {
this.l = i;
c(z ? "auto_quality" : "quality", PlayerTypes.c(i));
}
}
public void a(@Nullable String str, boolean z) {
String str2 = z ? "auto_subtitles" : "subtitles";
if (str == null) {
str = "off";
}
c(str2, str);
}
public void a(int i, int i2, @NonNull RewindType rewindType) {
Bundle b2 = b("rewind", i <= i2 ? "forward" : "backward");
b2.putString("rewind_type", rewindType.toString().toLowerCase());
b2.putInt("rewind_start", i);
b2.putInt("rewind_end", i2);
a(b2);
}
public void a(float f2) {
String format;
if (f2 == 1.0f) {
format = "normal";
} else {
int i = (int) f2;
format = f2 == ((float) i) ? String.format(Locale.US, "%s", i) : String.format(Locale.US, "%.2f", f2);
}
c("speed", format);
}
public void a(boolean z, @NonNull ResizeAction resizeAction) {
Bundle b2 = b("resize", z ? "widen" : "narrow");
b2.putString("resize_action", resizeAction.toString().toLowerCase());
a(b2);
}
public void a(String str, String str2) {
Bundle bundle = new Bundle();
bundle.putString("link_type", str);
bundle.putString("value", str2);
bundle.putString(NotificationCompat.CATEGORY_EVENT, "show_link");
a(bundle);
}
private void a(String str, int i) {
Bundle bundle = new Bundle();
bundle.putString("autoplay", this.f15641e ? "1" : "0");
bundle.putString("position", str);
if (i >= 0) {
bundle.putInt("quality", i - 2);
}
PlayerType playerType = this.m;
if (playerType != null) {
bundle.putString("player_type", playerType.toString().toLowerCase());
}
a("video_play", bundle);
}
private void a(Bundle bundle) {
a("video_event", bundle);
}
private void a(String str, Bundle bundle) {
bundle.putString("video_id", this.a.a + "_" + this.a.b);
bundle.putString(NavigatorKeys.V, this.f15639c);
bundle.putString(NavigatorKeys.Z, this.f15639c);
if (!TextUtils.isEmpty(this.f15640d)) {
bundle.putString("context", this.f15640d);
}
if (!TextUtils.isEmpty(this.a.l0)) {
bundle.putString(NavigatorKeys.l0, this.a.l0);
}
bundle.putLong("ts", System.currentTimeMillis());
StatisticsBridge1.a().a(str, bundle);
if (Preferences.getBoolValue("sendVideoMetrics", true) && str.equals("video_play")) {
try {
sendMetrics(str);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
public void sendMetrics(String event) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("e", event);
jsonObject.put("source", this.f15639c);
jsonObject.put("ref", this.f15639c);
jsonObject.put("video_id", this.a.a + "_" + this.a.b);
jsonObject.put("position", "start");
jsonObject.put("position_sec", 0);
jsonObject.put("cur_quality", "auto");
Metrics.trackEvents(jsonObject, false);
}
}
@@ -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,256 @@
package com.vk.music.stats;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import com.vk.core.extensions.StringExt;
import com.vk.music.common.MusicPlaybackLaunchContext;
import com.vk.music.logger.MusicLogger;
import com.vk.music.stats.i.MusicPlaybackParams;
import com.vk.navigation.NavigatorKeys;
import com.vtosters.lite.data.Analytics;
import kotlin.jvm.b.Functions2;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.Metrics;
import java.util.Objects;
import java.util.UUID;
public final class VKMusicStatsTracker implements MusicStatsTracker {
private String a;
private final Functions2<String, Analytics.l> b;
private static final a c = new a();
public VKMusicStatsTracker(Functions2<String, Analytics.l> functions2) {
this.b = functions2;
}
private String e(MusicPlaybackParams musicPlaybackParams) {
String g = musicPlaybackParams.g();
if (g == null) {
g = this.a;
}
if (g == null) {
g = "auto";
}
this.a = g;
return g;
}
@Override
public void a(boolean z) {
MusicStatsTracker.a.a(this, z);
}
@Override
public void b(MusicPlaybackParams musicPlaybackParams) {
c.a(a(musicPlaybackParams, "music_start_playback"));
if (Preferences.sendMusicMetrics()) {
try {
makeMetricsRequest(musicPlaybackParams, "music_start_playback");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void b(boolean z) {
}
@Override
public void c(MusicPlaybackParams musicPlaybackParams) {
MusicStatsTracker.a.b(this, musicPlaybackParams);
}
@Override
public void d() {
MusicStatsTracker.a.a(this);
}
@Override
public void a(Intent intent, String str) {
MusicStatsTracker.a.a(this, intent, str);
}
@Override
public void b(String str, @NonNull String str2) {
Analytics.l invoke = this.b.invoke("music_subscription_action");
invoke.a("popup", str);
invoke.a("action", str2);
c.a(invoke);
}
@Override
public void c(boolean z) {
MusicStatsTracker.a.b(this, z);
}
@Override
public void d(MusicPlaybackParams musicPlaybackParams) {
if (Objects.equals("pause", this.a)) {
a(musicPlaybackParams);
}
}
@Override
public void a(long j) {
MusicStatsTracker.a.a(this, j);
}
@Override
public void c() {
Analytics.l invoke = this.b.invoke("music_subscription_push");
invoke.a("action", "open");
c.a(invoke);
}
@Override
public void a(String str, MusicStatsRefer musicStatsRefer) {
Analytics.l invoke = this.b.invoke("music_subscription_show");
invoke.a("popup", str);
c.a(invoke);
}
@Override
public void b() {
Analytics.l invoke = this.b.invoke("music_subscription_push");
invoke.a("action", "send");
c.a(invoke);
}
@Override
public void a(MusicPlaybackParams musicPlaybackParams) {
c.a(a(musicPlaybackParams, "music_stop_playback"));
if (Preferences.sendMusicMetrics()) {
try {
makeMetricsRequest(musicPlaybackParams, "music_stop_playback");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void a(PlayerStateChangedParams playerStateChangedParams) {
Analytics.l invoke = this.b.invoke("audio_player");
invoke.a(NavigatorKeys.t0, playerStateChangedParams.a());
invoke.a("prev_state", playerStateChangedParams.c());
invoke.a("duration", playerStateChangedParams.b());
c.a(invoke);
}
@Override
public void b(String str) {
Analytics.l invoke = this.b.invoke("music_subscription_free_displayed");
invoke.a("popup", str);
c.a(invoke);
}
@Override
public void a(String str) {
Analytics.l invoke = this.b.invoke("playlist_start");
invoke.a(NavigatorKeys.e, str);
c.a(invoke);
}
private void makeMetricsRequest(MusicPlaybackParams musicPlaybackParams, String str) throws JSONException {
JSONObject event = new JSONObject();
if (str.equals("music_stop_playback")) {
event.put("e", str);
event.put("audio_id", musicPlaybackParams.c());
event.put("duration", musicPlaybackParams.a());
event.put("start_time", musicPlaybackParams.i());
event.put("shuffle", "false");
event.put("reason", "new");
event.put("state", "app");
event.put("duration", musicPlaybackParams.a());
} else {
event.put("e", str);
event.put("audio_id", musicPlaybackParams.c());
event.put("start_time", musicPlaybackParams.i());
event.put("shuffle", "false");
event.put("reason", "auto");
event.put("state", "app");
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
event.put("prev_audio_id", musicPlaybackParams.e());
}
}
Metrics.trackEvents(event, true);
}
private Analytics.l a(MusicPlaybackParams musicPlaybackParams, String str) {
Analytics.l invoke = this.b.invoke(str);
invoke.a("audio_id", musicPlaybackParams.c());
invoke.a("uuid", UUID.randomUUID().hashCode());
invoke.a("shuffle", musicPlaybackParams.l());
invoke.a("reason", e(musicPlaybackParams));
invoke.a("start_time", musicPlaybackParams.i());
invoke.a("playback_started_at", musicPlaybackParams.d());
invoke.a(NavigatorKeys.l0, musicPlaybackParams.j());
if (!Objects.equals("music_start_playback", str)) {
invoke.a("duration", musicPlaybackParams.a());
}
int i = h.$EnumSwitchMapping$0[musicPlaybackParams.b().ordinal()];
if (i == 1) {
invoke.a("repeat", "one");
} else if (i == 2) {
invoke.a("repeat", "all");
}
invoke.a(NavigatorKeys.t0, musicPlaybackParams.m());
MusicPlaybackLaunchContext h = musicPlaybackParams.h();
invoke.a(NavigatorKeys.V, h.v0());
if (h.v1()) {
invoke.a("playlist_id", h.u1());
}
if (h.i(4) || h.i(8)) {
invoke.a("expanded", h.i(4));
}
if (StringExt.a((CharSequence) musicPlaybackParams.e())) {
invoke.a("prev_audio_id", musicPlaybackParams.e());
}
if (StringExt.a((CharSequence) musicPlaybackParams.f())) {
invoke.a("prev_playlist_id", musicPlaybackParams.f());
}
return invoke;
}
@Override
public void a(String str, String str2) {
boolean a2 = Objects.equals("success", str2);
Analytics.l builder = this.b.invoke("music_subscription_purchase_result");
builder.a("popup", str);
builder.a(NotificationCompat.CATEGORY_STATUS, a2 ? "success" : "fail");
if (!a2) {
builder.a("reason", str2);
}
c.a(builder);
}
@Override
public void a() {
Analytics.l invoke = this.b.invoke("music_subscription");
invoke.a("popup", "background_v2");
invoke.a("action", "link");
c.a(invoke);
}
public static final class a {
private a() {
}
@VisibleForTesting
public void a(Analytics.l lVar) {
MusicLogger.a("MusicStats", "[VK_TRACKER]", lVar);
lVar.b();
}
}
}
@@ -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();
}
}
@@ -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,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;
}
}
@@ -11,37 +11,37 @@ import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
public class GmsHook {
private static final boolean needToSpoof = !isGmsInstalled() && (isFakeGmsInstalled() || isFakeGms2Installed());
private static final String[] spoofPackages = {"com.mgoogle", "app.revanced"};
private static final String currentGms;
static {
currentGms = getSpoofPackagePrefix();
}
private static boolean isPackageInstalled(String packageName) {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo(packageName, 0);
return true;
} catch (Exception unused) {
return false;
}
}
public static boolean isGmsInstalled() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("com.google.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Google Market Services check
return isPackageInstalled("com.google.android.gms");
}
public static boolean isFakeGmsInstalled() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("com.mgoogle.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Microg Google Market Services check
public static boolean isFakeGms2Installed() {
try {
AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo("app.revanced.android.gms", 0);
return true;
} catch (Exception unused) {
return false;
}
} // Microg Google Market Services check
public static boolean isSpoofPackageInstalled(String packageName) {
return isPackageInstalled(packageName + ".android.gms");
}
public static boolean isAnyServicesInstalled() {
return isGmsInstalled() || isFakeGmsInstalled() || isFakeGms2Installed();
for (String packageName : spoofPackages) {
if (isSpoofPackageInstalled(packageName)) {
return true;
}
}
return false;
}
public static void fixGapps() {
@@ -52,10 +52,10 @@ public class GmsHook {
notificationManager.createNotificationChannel(new NotificationChannel("audio_playback_channel", AndroidUtils.getResources().getString(R.string.audio_message_play_error), NotificationManager.IMPORTANCE_LOW));
}
}
} // Music channels fix
}
public static String replaceGMSPackage(String str) {
return needToSpoof ? str.replaceAll("com.google", (isFakeGms2Installed() ? "app.revanced" : "com.mgoogle")) : str;
return shouldSpoof() ? str.replaceAll("com.google", currentGms) : str;
}
public static String getFirebaseInstanceIdReceiver() {
@@ -63,6 +63,37 @@ public class GmsHook {
}
public static Class getFirebaseInstanceIdReceiverClass() {
return needToSpoof ? (isFakeGms2Installed() ? FirebaseInstanceIdReceiver3.class : FirebaseInstanceIdReceiver2.class) : FirebaseInstanceIdReceiver.class;
return getSpoofFirebaseInstanceIdReceiverClass();
}
}
public static String getCurrentGms() {
return currentGms;
}
private static boolean shouldSpoof() {
return !isGmsInstalled() && (isSpoofPackageInstalled("com.mgoogle") || isSpoofPackageInstalled("app.revanced"));
}
private static String getSpoofPackagePrefix() {
for (String packageName : spoofPackages) {
if (isSpoofPackageInstalled(packageName)) {
return packageName;
}
}
return "com.google";
}
private static Class getSpoofFirebaseInstanceIdReceiverClass() {
switch (currentGms) {
case "com.mgoogle" -> {
return FirebaseInstanceIdReceiver2.class;
}
case "app.revanced" -> {
return FirebaseInstanceIdReceiver3.class;
}
default -> {
return FirebaseInstanceIdReceiver.class;
}
}
}
}
@@ -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,7 +1,10 @@
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;
@@ -10,8 +13,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.IOException;
@@ -146,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) {
@@ -188,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;
@@ -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;
}
}
@@ -1,29 +1,40 @@
package ru.vtosters.hooks;
import androidx.core.util.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.RenameTool;
public class RenameHook {
public static void injectIntoJson(JSONObject obj) throws JSONException {
// int i = obj.getInt("id");
// if (RenameTool.updateRequested) {
// RenameTool.reloadDB();
// }
//
// Pair<String, String> user = RenameTool.renamedUsers.get(i);
// if (user == null) return;
// obj.put(RenameTool.COLUMN_FIRSTNAME, user.first).put(RenameTool.COLUMN_LASTNAME, user.second);
if (isEnabled()) {
int i = obj.getInt("id");
if (RenameTool.updateRequested) {
RenameTool.reloadDB();
}
Pair<String, String> user = RenameTool.renamedUsers.get(i);
if (user == null) return;
obj.put(RenameTool.COLUMN_FIRSTNAME, user.first).put(RenameTool.COLUMN_LASTNAME, user.second);
}
}
public static boolean isEnabled() {
return Preferences.getBoolValue("unstableNameChanger", true);
}
public static void injectIntoJsonGroup(JSONObject obj) throws JSONException {
// int i = obj.getInt("id");
//
// if (RenameTool.updateRequested) {
// RenameTool.reloadDB();
// }
//
// String user = RenameTool.renamedGroups.get(i);
// if (user == null) return;
// obj.put("name", user);
if (isEnabled()) {
int i = obj.getInt("id");
if (RenameTool.updateRequested) {
RenameTool.reloadDB();
}
String user = RenameTool.renamedGroups.get(i);
if (user == null) return;
obj.put("name", user);
}
}
}
@@ -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
);
}
}
}
@@ -6,15 +6,19 @@ import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import okhttp3.Headers;
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.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;
@@ -38,9 +42,15 @@ public class CatalogJsonInjector {
removeUnsupportedLayouts(blocks);
fetchCatalogId("https://vk.com/audio?section=my_playlists", oldItems);
if (Preferences.getBoolValue("playlistsCatalogs", true)) {
fetchCatalogId("https://vk.com/audio?section=my_playlists", oldItems);
fetchCatalogId("https://vk.com/audio?section=albums", oldItems);
fetchCatalogId("https://vk.com/audio?section=albums", oldItems);
}
if (Preferences.sendMusicMetrics() && Preferences.getBoolValue("playStatCatalog", false)) {
fetchCatalogId("https://vk.com/audio?section=recent", oldItems);
}
setDefaultAudioPage(oldItems, catalog);
}
@@ -48,19 +58,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());
@@ -87,6 +91,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
@@ -94,23 +99,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());
@@ -128,12 +123,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");
@@ -216,19 +214,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);
@@ -260,9 +272,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(Network.b(CLIENT_API).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");
@@ -276,4 +291,4 @@ public class CatalogJsonInjector {
Log.d("VTLMusic", "Error: " + e.getMessage());
}
}
}
}
@@ -15,18 +15,20 @@ import ru.vtosters.lite.downloaders.AudioDownloader;
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.AccountManagerUtils;
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,8 +37,10 @@ public class PlaylistInjector {
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
try {
if (!eligibleForOfflineCaching())
if (MusicCacheImpl.isEmpty()) {
return null;
}
var requestArgs = audioGetPlaylist.b();
var id = requestArgs.get("id");
@@ -44,11 +48,11 @@ public class PlaylistInjector {
var accessKey = requestArgs.get("access_key");
boolean isVirtualPlaylist = accessKey != null && (accessKey.equals("cache"));
boolean isAlbumVirtualPlaylist = accessKey != null && (accessKey.equals("cacheAlbum"));
boolean isOwnCachePlaylist = Objects.equals(ownerId, String.valueOf(AccountManagerUtils.getUserId())) && Objects.equals(id, "-1");
Log.d("PlaylistInjector", "id = " + id + " / owner = " + ownerId + " / access = " + accessKey + " / " + isVirtualPlaylist + " + " + isAlbumVirtualPlaylist);
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist))
if (TextUtils.isEmpty(id) || (!isVirtualPlaylist && !isAlbumVirtualPlaylist)) {
return null;
}
return Observable.a((ObservableOnSubscribe<AudioGetPlaylist.c>) observableEmitter -> {
AudioGetPlaylist.c response = new AudioGetPlaylist.c();
@@ -73,7 +77,7 @@ public class PlaylistInjector {
response.c = tracks;
} catch (RemoteException | JSONException e) {
e.printStackTrace();
e.fillInStackTrace();
response.c = new ArrayList<>();
}
observableEmitter.b(response);
@@ -82,18 +86,18 @@ public class PlaylistInjector {
return;
}
if (isVirtualPlaylist) {
if (isOwnCachePlaylist) {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getMyCachedMusicTracks();
response.b = PlaylistHelper.createCachedPlaylistMetadata();
} else {
response.c = (ArrayList<MusicTrack>) TracklistHelper.getTracks(requestArgs.get("owner_id"));
response.b = PlaylistHelper.createCachedPlaylistMetadata(requestArgs.get("owner_id"));
response.c = (ArrayList<MusicTrack>) MusicCacheImpl.getPlaylistSongs(ownerId, 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() {
@@ -39,7 +42,7 @@ public class TracklistInjector {
Log.d("TracklistInjector", "Using vkx cache catalog");
observableEmitter.b(parser.c(new JSONObject(service.getCacheCatalog())));
} catch (Exception e) {
e.printStackTrace();
e.fillInStackTrace();
List<MusicTrack> tracks = new ArrayList<>();
List<String> cache = service.getCache();
@@ -52,14 +55,14 @@ public class TracklistInjector {
observableEmitter.b(parser.c(createVirtualCatalog(tracks)));
}
} catch (Exception e) {
e.printStackTrace();
e.fillInStackTrace();
}
});
} else {
if (MusicCacheImpl.isEmpty()) {
observableEmitter.b(parser.c(getEmptyCatalog()));
} else {
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getMyCachedMusicTracks())));
}
}
});
@@ -162,16 +165,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 +187,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 +195,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 +212,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 +239,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 +270,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);
@@ -97,8 +100,8 @@ public class Preferences {
return getBoolValue("vkme", false);
}
public static boolean autocache() {
return getBoolValue("autocache", false);
public static int autocache() {
return Preferences.getPreferences().getInt("autocaching", 0);
}
public static boolean adsstories() {
@@ -289,6 +292,10 @@ public class Preferences {
return getBoolValue("vkme_notifs", false);
}
public static boolean sendMusicMetrics() {
return getBoolValue("sendMusicMetrics", true);
}
public static boolean screenshotdetect() {
return getBoolValue("screenshotdetect", true);
}
@@ -311,6 +318,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 +363,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));
}
}
}
}
@@ -115,7 +115,7 @@ public class Device {
+ "- Manufacturer: " + this.manufacturerName + "\n"
+ "- Brand: " + this.brandName + "\n"
+ "- Model: " + this.modelName + "\n"
+ (isMIUI() ? "- Miui Version Code: " + this.MiuiUiVersionCode + "\n" + "- Miui Version Name: " + this.MiuiUiVersionName + "\n" : "")
+ (isMIUI() || isHyperOs() ? "- Miui/HyperOS Version Code: " + this.MiuiUiVersionCode + "\n" + "- Miui/HyperOS Version Name: " + this.MiuiUiVersionName + "\n" : "")
+ (isSamsung() ? "- OneUi Major Version: " + this.OneUiMajorVersion + "\n" + "- OneUi Minor Version: " + this.OneUiMinorVersion + "\n" : "")
+ (hasMiuiIncrCode() ? "- Miui Version Incremental Code: " + this.MiuiIncrementalCode + "\n" : "")
+ (isEMUI() ? "- Emui Version: " + this.EmuiVersion : "");
@@ -56,7 +56,7 @@ public class OEMDetector {
}
public static boolean isOEM() {
return isMIUI() || isEMUI() || isFlyme() || isZenUI() || isSamsung() || isVivo() || isOneUi();
return isMIUI() || isEMUI() || isFlyme() || isZenUI() || isSamsung() || isVivo() || isOneUi() || isHyperOs();
}
public static boolean isMIUI() {
@@ -68,6 +68,15 @@ public class OEMDetector {
}
}
public static boolean isHyperOs() {
try {
int miuiVer = Integer.parseInt(getMiuiUiVersionCode()); // cuz hyperos gives 816 and etc
return miuiVer > 14 && isMIUI();
} catch (Exception unused) {
return false;
}
}
public static boolean isIntentResolved(Context ctx, Intent intent) {
return (intent != null && ctx.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null);
}
@@ -124,7 +133,6 @@ public class OEMDetector {
try {
return (String) getPropMtd.invoke(null, prop);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
@@ -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);
}
}
}
}
@@ -5,10 +5,7 @@ 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.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.*;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.File;
@@ -41,45 +38,43 @@ public class StoryDownloader {
.b(url)
.a();
try (Response response = client.a(request).execute()) {
if (!response.h()) {
throw new IOException("Unexpected code " + response);
}
ResponseBody responseBody = response.a();
if (responseBody == null) {
throw new IOException("No response body");
}
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) {
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
} catch (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,8 +1,11 @@
package ru.vtosters.lite.downloaders;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
@@ -10,6 +13,7 @@ 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;
@@ -23,6 +27,7 @@ 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;
@@ -39,6 +44,9 @@ import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class VideoDownloader {
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]));
@@ -73,46 +81,86 @@ public class VideoDownloader {
.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();
try (Response response = client.a(request).execute()) {
if (!response.h()) {
throw new IOException("Unexpected code " + 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;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
MediaScannerConnection.scanFile(finalContext, new String[]{outputFile.toString()}, null, null);
AndroidUtils.sendToast("Файл скачан в Movies/VKVideo");
} catch (IOException e) {
client.a(request).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
@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();
}
@@ -168,6 +216,7 @@ public class VideoDownloader {
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() {
@@ -183,7 +232,7 @@ public class VideoDownloader {
progressDialog.cancel();
try {
JSONObject mainJson = new JSONObject(response.a().g());
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
JSONObject responseJson = mainJson.getJSONObject("response").optJSONArray("items").optJSONObject(0);
StoryEntry story = new StoryEntry(responseJson);
StoryDownloader.downloadStory(story);
@@ -211,6 +260,7 @@ public class VideoDownloader {
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();
Network.b(CLIENT_API).a(req).a(new Callback() {
@@ -225,7 +275,7 @@ public class VideoDownloader {
progressDialog.cancel();
try {
JSONObject mainJson = new JSONObject(response.a().g());
JSONObject mainJson = new JSONObject(GzipDecompressor.decompressResponse(response));
JSONObject responseJson = mainJson.getJSONObject("response");
JSONArray itemsJson = responseJson.getJSONArray("items");
@@ -238,4 +288,4 @@ public class VideoDownloader {
}
});
}
}
}
@@ -18,6 +18,7 @@ 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;
@@ -106,6 +107,7 @@ public class MessagesDownloader {
AccountManagerUtils.getUserToken());
var req = new Request.a()
.b(url)
.a("Accept-Encoding", "gzip")
.a();
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
@@ -116,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;
@@ -15,6 +17,7 @@ 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;
@@ -26,6 +29,8 @@ import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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=\"(.+?)\"");
private static final String DELETED = "<ya:profileState>deleted</ya:profileState>";
@@ -35,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());
@@ -52,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());
@@ -65,9 +72,9 @@ public class FoafBase {
}
}
@SuppressLint("DefaultLocale")
private static String getLink(int i) {
String prefix = (ProxyUtils.isAnyProxyEnabled() || ProxyUtils.isVKProxyEnabled()) ? "https://" + (ProxyUtils.isVKProxyEnabled() ? VikaMobile.getApiHost() : ProxyUtils.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) {
@@ -78,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);
}
@@ -119,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,21 @@ 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.AccountManagerUtils;
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 +43,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;
@@ -40,13 +51,14 @@ public class LastFMScrobbler {
var access_key = !TextUtils.isEmpty(musictrack.J) ? musictrack.J : "";
var album_id = musictrack.I;
var album = album_id != null ? album_id.getTitle() : null;
var isOwnTrack = musictrack.e == AccountManagerUtils.getUserId();
if (TextUtils.isEmpty(uid) || TextUtils.isEmpty(artist) || TextUtils.isEmpty(title) || duration == 0) {
Log.d("Scrobbler", "grabTrackInfo: " + "Empty track, info: " + artist + " - " + title + " - " + duration + " - " + uid);
return;
}
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
if (needToCache(isOwnTrack) && !MusicCacheImpl.isCachedTrack(uid)) {
if (LibVKXClient.isIntegrationEnabled()) {
LibVKXClient.getInstance().runOnService(service -> {
try {
@@ -63,6 +75,12 @@ public class LastFMScrobbler {
scrobbleTrack(duration, artist, title, uid, album);
}
private static boolean needToCache(boolean isOwnTrack) {
int autocache = Preferences.autocache();
return autocache == 2 || (autocache == 1 && isOwnTrack);
}
public static void scrobbleTrack(
long duration,
String artist,
@@ -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());
}
@@ -7,10 +7,12 @@ import com.vk.dto.music.Playlist;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
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;
@@ -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 Network.b(CLIENT_API).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
}
}
}
}
@@ -7,9 +7,11 @@ import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import okhttp3.Headers;
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;
@@ -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 Network.b(CLIENT_API).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,111 @@
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 com.vk.dto.music.Playlist;
import java.io.File;
import java.io.IOException;
import java.net.URL;
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 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);
}
if (Objects.requireNonNull(uri).contains("master.m3u8?siren=1")) {
try {
String content = IOUtils.readAllLines(new URL(uri).openStream());
String replacement = content.split("\n")[2].trim();
uri = uri.replace("master.m3u8?siren=1", replacement);
} catch (IOException e) {
callback.onFailure(e);
return;
}
}
var tsParser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(uri));
var baseUri = uri.substring(0, uri.lastIndexOf("/") + 1);
try {
var segments = ((f) tsParser.a(Uri.parse(baseUri), IOUtils.openStream(uri))).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);
}
@@ -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();
}
@@ -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());
}
}
@@ -10,7 +10,7 @@ import ru.vtosters.lite.utils.NavigatorUtils;
public class InstallGMS {
public static void alert(final Activity activity) {
if (!GmsHook.isAnyServicesInstalled() && Preferences.getBoolValue("showAlertGms", true)) {
if (!GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled() && Preferences.getBoolValue("showAlertGms", true)) {
new VkAlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.warning))
.setMessage(activity.getString(R.string.gmsissuesumm))
@@ -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;
}
}
@@ -1,29 +1,40 @@
package ru.vtosters.lite.ui.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import androidx.preference.Preference;
import android.widget.TextView;
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.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
private static final ExecutorService executor = Executors.newCachedThreadPool();
@SuppressLint("DefaultLocale")
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -50,6 +61,20 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
}
);
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",
@@ -75,15 +100,38 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
}
).setEnabled(!MusicCacheImpl.isEmpty());
PreferenceFragmentUtils.addMaterialSwitchPreference(
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"autocache",
"autocache_params",
getString(com.vtosters.lite.R.string.autocache_title),
getString(com.vtosters.lite.R.string.autocache_summ),
getAutocacheSumm(),
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("autocache", (boolean) o).apply();
preference -> {
List<String> items = Arrays.asList("Не кешировать", "Только свои", "Все");
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, items) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
};
int selectedItem = Preferences.getPreferences().getInt("autocaching", 0);
if (selectedItem >= 0 && selectedItem < items.size()) {
adapter.getItem(selectedItem);
}
new VkAlertDialog.Builder(getActivity())
.setAdapter(adapter, (dialog, which) -> {
adapter.getItem(which);
Preferences.getPreferences().edit().putInt("autocaching", which).apply();
findPreference("autocache_params").setSummary(getAutocacheSumm());
dialog.cancel();
})
.show();
return true;
}
);
@@ -99,6 +147,7 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("dldir", (boolean) o).apply();
LifecycleUtils.restartApplicationWithTimer();
return true;
}
);
@@ -116,6 +165,73 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
}
);
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());
findPreference("metadataSeparator").setSummary(separator.getText().toString());
})
.setNegativeButton("Отмена", (dialog, which) -> dialog.cancel())
.show();
return true;
}
);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Рекомендации музыки");
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"sendMusicMetrics",
"Отправлять статистику",
"Статистика прослушиваний необходима для работы рекомендаций и истории прослушивания музыки",
null,
true,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("sendMusicMetrics", (boolean) o).apply();
return true;
}
);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"playStatCatalog",
"Список истории прослушиваний",
"Показывать вкладку истории прослушиваний в музыкальном разделе\n\nОтключение ускорит открытие музыкального раздела при медленном интернете",
null,
false,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("playStatCatalog", (boolean) o).apply();
return true;
}
).setVisible(Preferences.sendMusicMetrics() && !getBoolValue("useOldAppVer", false));
if (!Preferences.serverFeaturesDisable()) {
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), "Интеграция Genius");
PreferenceFragmentUtils.addMaterialSwitchPreference(
@@ -223,6 +339,19 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
list.setVisible(!Preferences.getBoolValue("useOldAppVer", false));
getPreferenceScreen().addPreference(list);
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"playlistsCatalogs",
"Списки плейлистов",
"Показывать вкладку плейлистов и альбомов прослушиваний в музыкальном разделе\n\nОтключение ускорит открытие музыкального раздела при медленном интернете",
null,
true,
(preference, o) -> {
Preferences.getPreferences().edit().putBoolean("playlistsCatalogs", (boolean) o).apply();
return true;
}
).setVisible(!getBoolValue("useOldAppVer", false));
}
private void lastfmAuth(Context ctx) {
@@ -262,6 +391,15 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
.show();
}
private static String getAutocacheSumm() {
int autocache = Preferences.autocache();
return switch (autocache) {
case 1 -> "Только для своих";
case 2 -> "Кешировать всё";
default -> "Отключено";
};
}
public void updateLastFmPref() {
if (LastFMScrobbler.isLoggedIn()) {
findPreference("lastfm_auth").setSummary(getString(com.vtosters.lite.R.string.lastfm_authorized_as) + " " + LastFMScrobbler.getUserName());
@@ -283,14 +421,37 @@ public class MusicFragment extends TrackedMaterialPreferenceToolbarFragment {
.show();
}
@SuppressLint("DefaultLocale")
private 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("Плейлист удален");
findPreference("cached_playlists").setSummary(String.format("Скачано плейлистов: %d", MusicCacheImpl.getPlaylists().size()));
});
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())
.setPositiveButton(com.vtosters.lite.R.string.yes, (dialog, which) -> {
executor.submit(MusicCacheImpl::clear);
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(com.vtosters.lite.R.string.cached_tracks_counter), MusicCacheImpl.getTracksCount()));
})
.setNeutralButton(com.vtosters.lite.R.string.no, (dialog, which) -> dialog.cancel())
.show();
}
@@ -60,12 +60,12 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("microgsettings").setVisible(GmsHook.isFakeGmsInstalled() && !GmsHook.isGmsInstalled());
findPreference("microgsettings").setVisible(GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled());
findPreference("microgsettings").setOnPreferenceClickListener(preference -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName((GmsHook.isFakeGms2Installed() ? "app.revanced.android.gms" : "com.mgoogle.android.gms"), "org.microg.gms.ui.SettingsActivity"));
intent.setComponent(new ComponentName(GmsHook.getCurrentGms() + ".android.gms", "org.microg.gms.ui.SettingsActivity"));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
@@ -81,6 +81,17 @@ public class OtherFragment extends TrackedMaterialPreferenceToolbarFragment {
return true;
});
findPreference("unstableNameChangerDrop").setOnPreferenceClickListener(preference -> {
RenameTool.clearDatabase();
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("unstableNameChanger").setOnPreferenceChangeListener((preference, o) -> {
LifecycleUtils.restartApplicationWithTimer();
return true;
});
findPreference("copydebuginfo").setOnPreferenceClickListener(preference -> {
copyText(new DeviceInfoCollector().collect().forLogging());
Toast.makeText(requireContext(), AndroidUtils.getString("device_info_copied"), Toast.LENGTH_SHORT).show();
@@ -92,6 +103,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 +121,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;
@@ -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,48 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
);
}
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"",
"Не блокировать посты",
"Помечать посты заблокированные фильтрами",
null,
FiltersPreferences.isEnabledMarking(),
(preference, o) -> {
FiltersPreferences.setEnabledMarking((boolean) o);
return true;
}
);
if (NetworkUtils.isNetworkConnected()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Обновить списки фильтров",
null,
null,
preference -> {
this.requireActivity().runOnUiThread(() -> {
FiltersPreferences.forceUpdateFilters();
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/3559732")));
return false;
}
);
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -139,6 +272,20 @@ public class SponsorPostFragment extends TrackedMaterialPreferenceToolbarFragmen
}
}
);
if (Preferences.dev()) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
"Сбросить токен пользователя",
null,
null,
preference -> {
VotesPreferences.remUserToken();
return false;
}
);
}
}
@Override
@@ -13,6 +13,8 @@ import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.VersionReader;
import static ru.vtosters.hooks.GmsHook.isSpoofPackageInstalled;
public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
@Override
@@ -46,11 +48,11 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
boolean isZenUI = OEMDetector.isZenUI();
boolean isEMUI = OEMDetector.isEMUI();
boolean hasMiuiIncrCode = OEMDetector.hasMiuiIncrCode();
boolean isHyperOS = OEMDetector.isHyperOs();
boolean hasGMS = GmsHook.isGmsInstalled();
boolean hasVancedMicroG = GmsHook.isFakeGmsInstalled();
boolean hasReVancedMicroG = GmsHook.isFakeGms2Installed();
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? "com.mgoogle.android.gms" : hasReVancedMicroG ? "app.revanced.android.gms" : "no gms or microg";
boolean hasVancedMicroG = GmsHook.isAnyServicesInstalled();
String GMSPackageName = hasGMS ? "com.google.android.gms" : hasVancedMicroG ? GmsHook.getCurrentGms() + ".android.gms" : "no gms or microg";
boolean isValidSignature = Preferences.isValidSignature();
boolean isTablet = AndroidUtils.isTablet();
@@ -104,9 +106,7 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledGoogleGMS", "Value: " + hasGMS, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledVancedMicroG", "Value: " + hasVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledReVancedMicroG", "Value: " + hasReVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isInstalledMicroG", "Value: " + hasVancedMicroG, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "Product Name", productName, 0, preference -> {
((ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("MBH-ST", productName));
@@ -194,6 +194,8 @@ public class SystemInfo extends TrackedMaterialPreferenceToolbarFragment {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isMiui", "Value: " + isMiui, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isHyperOS", "Value: " + isHyperOS, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isEMUI", "Value: " + isEMUI, 0, null);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", "isFlyme", "Value: " + isFlyme, 0, null);
@@ -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);
@@ -46,6 +46,7 @@ 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.*;
@@ -107,7 +108,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
boolean isLinksUnverified = AndroidUtils.isLinksUnverified(requireActivity());
boolean isDozingAvailable = Build.VERSION.SDK_INT >= 23 && !manager.isIgnoringBatteryOptimizations(AndroidUtils.getPackageName());
boolean areNotificationsDisabled = !AndroidUtils.areNotificationsEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled();
boolean isGMSNotInstalled = !GmsHook.isAnyServicesInstalled() && !GmsHook.isGmsInstalled();
if (AccountManagerUtils.isLogin()) {
Preference accountSwitcher = PreferenceFragmentUtils.addPreference(
@@ -533,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",
@@ -569,7 +584,7 @@ public class VTSettings extends TrackedMaterialPreferenceToolbarFragment {
}
);
if (Build.VERSION.SDK_INT >= 33 && !OEMDetector.isMIUI()) {
if (Build.VERSION.SDK_INT >= 33 && (!OEMDetector.isMIUI() || OEMDetector.isHyperOs())) {
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
@@ -14,10 +14,12 @@ import com.vtosters.lite.api.ExtendedCommunityProfile;
import com.vtosters.lite.api.ExtendedUserProfile;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import ru.vtosters.hooks.RenameHook;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.foaf.FoafBase;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.RenameTool;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -66,10 +68,12 @@ public class MenuBuilder {
return Unit.a;
});
// addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
// RenameTool.createDialog(eup, ctx);
// return Unit.a;
// });
if (RenameHook.isEnabled()) {
addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
RenameTool.createDialog(eup, ctx);
return Unit.a;
});
}
addItem(builder, AndroidUtils.getString(isPageWhitelistedFilter ? R.string.remove_from_filter_whitelist : R.string.add_to_filter_whitelist), () -> {
setWhitelistedFilter(eup, !isPageWhitelistedFilter);
@@ -83,7 +87,7 @@ public class MenuBuilder {
return Unit.a;
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
e.fillInStackTrace();
}
}
@@ -100,10 +104,12 @@ public class MenuBuilder {
return Unit.a;
});
// addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
// RenameTool.createDialogGroup(ecp, ctx);
// return Unit.a;
// });
if (RenameHook.isEnabled()) {
addItem(builder, AndroidUtils.getString(R.string.menu_change_name), () -> {
RenameTool.createDialogGroup(ecp, ctx);
return Unit.a;
});
}
addItem(builder, AndroidUtils.getString(isPageWhitelistedFilter ? R.string.remove_from_filter_whitelist : R.string.add_to_filter_whitelist), () -> {
setWhitelistedFilter(ecp, !isPageWhitelistedFilter);
@@ -123,7 +129,7 @@ public class MenuBuilder {
return Unit.a;
});
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
e.fillInStackTrace();
}
}
@@ -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();
@@ -0,0 +1,107 @@
package ru.vtosters.lite.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.util.Log;
import com.vk.core.network.Network;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import static com.vk.core.network.Network.ClientType.CLIENT_API;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
public class Metrics {
private static JSONArray events = new JSONArray();
public static void trackEvents(JSONObject object, boolean isMusic) {
if (NetworkUtils.isNetworkConnected()) {
handleNetworkConnected(object, isMusic);
} else {
handleNetworkDisconnected(object, isMusic);
}
}
private static void handleNetworkConnected(JSONObject object, boolean isMusic) {
if (isMusic || shouldSaveUserTraffic() || events.length() > 0) {
trackEventsImmediately(object);
} else {
trackEventList(object);
}
}
private static void handleNetworkDisconnected(JSONObject object, boolean isMusic) {
if (!isMusic) {
events.put(object);
}
}
private static boolean shouldSaveUserTraffic() {
return getBoolValue("isRoamingState", false) || isMobileNetwork();
}
private static void trackEventList(JSONObject object) {
events.put(object);
if (events.length() > 2) {
trackEventsRequest(events);
resetEvents();
}
}
private static void trackEventsImmediately(JSONObject object) {
JSONArray eventsToTrack = new JSONArray();
eventsToTrack.put(object);
trackEventsRequest(eventsToTrack);
}
private static void resetEvents() {
events = new JSONArray();
}
@SuppressWarnings("deprecation")
private static boolean isMobileNetwork() {
ConnectivityManager connectivityManager = (ConnectivityManager) AndroidUtils.getGlobalContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
android.net.Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
return networkCapabilities != null && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
}
} else {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo != null && networkInfo.isConnected();
}
return false;
}
private static void trackEventsRequest(JSONArray object) {
Request req = new Request.a()
.b("https://" + getApi() + "/method/stats.trackEvents?events=" + object.toString() + "&v=5.119&access_token=" + getUserToken())
.a();
Network.b(CLIENT_API).a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
e.fillInStackTrace();
}
@Override
public void a(Call call, Response response) throws IOException {
Log.d("Metrics", response.a().g());
}
});
}
}
@@ -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);
}
}
}
@@ -1,5 +1,6 @@
package ru.vtosters.lite.utils;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -12,15 +13,15 @@ import android.widget.LinearLayout;
import androidx.core.util.Pair;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.dto.user.UserProfile;
import com.vk.im.engine.models.users.User;
import com.vtosters.lite.R;
import com.vtosters.lite.api.ExtendedCommunityProfile;
import com.vtosters.lite.api.ExtendedUserProfile;
import ru.vtosters.hooks.other.ThemesUtils;
import java.lang.reflect.Field;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.function.BiConsumer;
import static ru.vtosters.hooks.other.ThemesUtils.getSTextAttr;
import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr;
@@ -47,69 +48,40 @@ public class RenameTool {
return helperInstance;
}
public static String getCurrentModifiedUser() {
Cursor rawQuery = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s WHERE %s=%s", TABLE_NAME, COLUMN_VKID, AccountManagerUtils.getUserId()), new String[0]);
if (rawQuery.moveToFirst()) {
try {
String decode = URLDecoder.decode(rawQuery.getString(rawQuery.getColumnIndexOrThrow(COLUMN_FIRSTNAME)), "UTF-8");
return decode + " " + URLDecoder.decode(rawQuery.getString(rawQuery.getColumnIndexOrThrow(COLUMN_LASTNAME)), "UTF-8");
} catch (java.io.UnsupportedEncodingException | java.lang.IllegalArgumentException e) {
e.printStackTrace();
} finally {
rawQuery.close();
}
} else rawQuery.close();
return null;
}
// Reload all values from DB
public static void reloadDB() {
renamedGroups.clear();
renamedUsers.clear();
updateRequested = false;
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s", TABLE_NAME), new String[0])) {
while (cursor.moveToNext()) {
String firstName = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FIRSTNAME)), "UTF-8");
String lastName = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LASTNAME)), "UTF-8");
int vkID = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_VKID));
renamedUsers.put(vkID, new Pair<>(firstName, lastName));
}
} catch (Exception e) {
e.printStackTrace();
}
loadData(TABLE_NAME, (cursor, vkID) -> {
String firstName = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FIRSTNAME)));
String lastName = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LASTNAME)));
renamedUsers.put(vkID, new Pair<>(firstName, lastName));
});
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(String.format("SELECT * FROM %s", TABLE_NAME_GROUP), new String[0])) {
loadData(TABLE_NAME_GROUP, (cursor, vkID) -> {
String name = decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)));
renamedGroups.put(vkID, name);
});
}
// Generic method to load data from the database
private static void loadData(String tableName, BiConsumer<Cursor, Integer> consumer) {
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery("SELECT * FROM " + tableName, null)) {
while (cursor.moveToNext()) {
String name = URLDecoder.decode(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME)), "UTF-8");
int vkID = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_VKID));
renamedGroups.put(vkID, name);
consumer.accept(cursor, vkID);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to load data from " + tableName, e);
}
}
public static void injectIntoChat(User u) {
int id = u.getId();
if (updateRequested) {
reloadDB();
}
Pair<String, String> user = renamedUsers.get(id);
if (user == null) return;
setObject(u, "I", user.first);
setObject(u, "J", user.second);
}
public static void setObject(User instance, String name, Object obj) {
private static String decode(String value) {
try {
Field f = User.class.getDeclaredField(name);
f.setAccessible(true);
f.set(instance, obj);
} catch (Exception e) {
e.printStackTrace();
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
@@ -117,7 +89,7 @@ public class RenameTool {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
final EditText fn = new EditText(ctx);
EditText fn = new EditText(ctx);
fn.setHint(R.string.vk_auth_sign_up_first_name);
fn.setTextColor(getTextAttr());
fn.setHintTextColor(getSTextAttr());
@@ -128,7 +100,7 @@ public class RenameTool {
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
fn.setLayoutParams(margin);
final EditText ln = new EditText(ctx);
EditText ln = new EditText(ctx);
ln.setHint(R.string.vk_auth_sign_up_last_name);
ln.setTextColor(getTextAttr());
ln.setHintTextColor(getSTextAttr());
@@ -137,69 +109,93 @@ public class RenameTool {
ln.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
ln.setLayoutParams(margin);
final UserProfile profile = AccountManagerUtils.fromEup(eup);
UserProfile profile = AccountManagerUtils.fromEup(eup);
fn.setText(AccountManagerUtils.getUserFirstName(profile));
ln.setText(AccountManagerUtils.getUserLastName(profile));
int id = AccountManagerUtils.getUserID(profile);
var builder = new VkAlertDialog.Builder(ctx)
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx)
.setTitle(R.string.rename_title)
.setMessage(R.string.rename_message)
.setView(linearLayout)
.setPositiveButton("OK", (dialog, which) -> {
String firstName = fn.getText().toString();
String lastName = ln.getText().toString();
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
if (isChangedName(id)) {
writableDatabase.execSQL(
String.format("UPDATE %s SET %s='%s', %s='%s' WHERE %s='%s'",
TABLE_NAME,
COLUMN_FIRSTNAME, URLEncoder.encode(firstName, "UTF-8"),
COLUMN_LASTNAME, URLEncoder.encode(lastName, "UTF-8"),
COLUMN_VKID, id));
} else {
writableDatabase.execSQL(
String.format("INSERT INTO %s (%s, %s, %s) VALUES (%s, '%s', '%s')",
TABLE_NAME,
COLUMN_VKID, COLUMN_FIRSTNAME, COLUMN_LASTNAME,
id, URLEncoder.encode(firstName, "UTF-8"), URLEncoder.encode(lastName, "UTF-8")));
}
updateRequested = true;
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
.setPositiveButton("OK", (dialog, which) -> updateName(ctx, fn.getText().toString(), ln.getText().toString(), id));
if (isChangedName(id)) {
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> deleteUser(ctx, id));
}
sendToast(ctx.getString(R.string.rename_success));
} catch (Exception e) {
e.printStackTrace();
sendToast(ctx.getString(R.string.rename_error));
}
});
if (isChangedName(id))
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> {
getHelper().getWritableDatabase().execSQL(
String.format(
"DELETE FROM %s WHERE %s='%s'",
TABLE_NAME,
COLUMN_VKID, id));
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
updateRequested = true;
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
sendToast(ctx.getString(R.string.rename_remove_from_bd_success));
});
builder.show();
}
public static void deleteUser(Context ctx, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
updateRequested = true;
notifyUser(ctx, id);
sendToast(ctx.getString(R.string.rename_remove_from_bd_success));
}
public static void clearDatabase() {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME, null, null);
writableDatabase.delete(TABLE_NAME_GROUP, null, null);
renamedGroups.clear();
renamedUsers.clear();
updateRequested = true;
}
public static void updateName(Context ctx, String firstName, String lastName, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_FIRSTNAME, encode(firstName));
values.put(COLUMN_LASTNAME, encode(lastName));
if (isChangedName(id)) {
// Update existing record
writableDatabase.update(TABLE_NAME, values, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
} else {
// Insert new record
values.put(COLUMN_VKID, id);
writableDatabase.insert(TABLE_NAME, null, values);
}
updateRequested = true;
notifyUser(ctx, id);
sendToast(ctx.getString(R.string.rename_success));
} catch (Exception e) {
throw new RuntimeException("Database operation failed", e);
}
}
private static String encode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
private static void notifyUser(Context ctx, int id) {
if (id == AccountManagerUtils.getUserId()) {
ctx.sendBroadcast(new Intent("com.vkontakte.android.USER_NAME_CHANGED"));
}
ctx.sendBroadcast(new Intent("com.vkontakte.android.ACTION_PROFILE_UPDATED").putExtra("uid", id));
}
public static void createDialogGroup(ExtendedCommunityProfile eup, final Context ctx) {
LinearLayout linearLayout = new LinearLayout(ctx);
linearLayout.setOrientation(LinearLayout.VERTICAL);
final EditText fn = new EditText(ctx);
EditText fn = new EditText(ctx);
fn.setHint(R.string.vk_auth_sign_up_first_name);
fn.setHintTextColor(getSTextAttr());
fn.setTextColor(getTextAttr());
@@ -210,52 +206,61 @@ public class RenameTool {
margin.setMargins(dp2px(20f), 0, dp2px(20f), 0);
fn.setLayoutParams(margin);
final UserProfile profile = AccountManagerUtils.fromEup(eup);
UserProfile profile = AccountManagerUtils.fromEup(eup);
fn.setText(AccountManagerUtils.getGroupName(profile));
int fid = AccountManagerUtils.getUserID(profile);
if (fid < 0) fid = -fid;
final int id = fid;
int id = fid;
var builder = new VkAlertDialog.Builder(ctx)
VkAlertDialog.Builder builder = new VkAlertDialog.Builder(ctx)
.setTitle(R.string.rename_title)
.setMessage(R.string.rename_message)
.setView(linearLayout)
.setPositiveButton("OK", (dialog, which) -> {
String firstName = fn.getText().toString();
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
if (isChangedNameGroup(id)) {
writableDatabase.execSQL(
String.format(
"UPDATE %s SET %s='%s' WHERE %s='%s'",
TABLE_NAME_GROUP,
COLUMN_NAME, URLEncoder.encode(firstName, "UTF-8"),
COLUMN_VKID, id));
} else {
writableDatabase.execSQL(
String.format(
"INSERT INTO %s (%s, %s) VALUES (%s, '%s')",
TABLE_NAME_GROUP,
COLUMN_VKID, COLUMN_NAME,
id, URLEncoder.encode(firstName, "UTF-8")));
}
updateRequested = true;
sendToast(AndroidUtils.getString(R.string.rename_group_success));
} catch (Exception e) {
e.printStackTrace();
}
});
if (isChangedNameGroup(id))
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> {
getHelper().getWritableDatabase().execSQL(String.format("DELETE FROM %s WHERE %s='%s'", TABLE_NAME_GROUP, COLUMN_VKID, id));
updateRequested = true;
sendToast(ctx.getString(R.string.rename_remove_group_from_bd_success));
});
.setPositiveButton("OK", (dialog, which) -> updateGroupName(ctx, fn.getText().toString(), id));
if (isChangedNameGroup(id)) {
builder.setNeutralButton(R.string.rename_reset, (dialog, which) -> deleteGroupName(ctx, id));
}
builder.show();
}
public static void deleteGroupName(Context ctx, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
writableDatabase.delete(TABLE_NAME_GROUP, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
updateRequested = true;
sendToast(ctx.getString(R.string.rename_remove_group_from_bd_success));
}
public static void updateGroupName(Context ctx, String groupName, int id) {
SQLiteDatabase writableDatabase = getHelper().getWritableDatabase();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_NAME, encode(groupName));
if (isChangedNameGroup(id)) {
// Update existing record
writableDatabase.update(TABLE_NAME_GROUP, values, COLUMN_VKID + "=?", new String[]{String.valueOf(id)});
} else {
// Insert new record
values.put(COLUMN_VKID, id);
writableDatabase.insert(TABLE_NAME_GROUP, null, values);
}
updateRequested = true;
sendToast(AndroidUtils.getString(R.string.rename_group_success));
} catch (Exception e) {
throw new RuntimeException("Database operation failed", e);
}
}
public static boolean isChangedName(int uid) {
return renamedUsers.get(uid) != null;
}
@@ -264,10 +269,6 @@ public class RenameTool {
return renamedGroups.get(uid) != null;
}
public static boolean isIdInList(UserProfile profile) {
return true;
}
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
@@ -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());
}
}
}
@@ -0,0 +1,18 @@
package ru.vtosters.lite.utils.music;
import com.vk.dto.music.MusicTrack;
import com.vk.dto.music.Artist;
import java.util.stream.Collectors;
public class MusicTrackUtils {
public static String getArtists(MusicTrack musicTrack) {
return (musicTrack.L == null || musicTrack.I == null
? normalizeMetadata(musicTrack.C)
: normalizeMetadata(musicTrack.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
}
private static String normalizeMetadata(String in) {
return in.replaceAll("[\\\\/:*?\"<>|]", "");
}
}
@@ -0,0 +1,17 @@
package ru.vtosters.lite.utils.music;
import android.util.Log;
import com.vk.dto.music.Playlist;
import org.json.JSONObject;
public class PlaylistUtils {
public static JSONObject getThumb(Playlist playlist) {
try {
Log.d("Playlist", "thumb is not null");
return playlist.F.J();
} catch (Exception e) {
Log.d("Playlist", "thumb is null");
return null;
}
}
}
@@ -6,6 +6,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.utils.FiltersPreferences;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
@@ -185,7 +186,7 @@ public class NewsFeedFiltersUtils {
return json;
}
private static boolean needToSave(JSONObject post, String source, boolean needToShowToast) throws JSONException {
private static boolean needToSave(JSONObject post, String source, boolean needToNotifyBlock) throws JSONException {
String type = post.optString("type");
if (isAds(post, type)) {
@@ -198,58 +199,84 @@ public class NewsFeedFiltersUtils {
|| type.endsWith("playlists")
|| type.endsWith("groups")))) {
logRemovedPost(post, source, "authors", needToShowToast);
logRemovedPost(post, source, "authors", needToNotifyBlock);
return false;
}
if (postsrecomm() && (type.equals("inline_user_rec") || type.equals("live_recommended"))) {
logRemovedPost(post, source, "postsrecomm", needToShowToast);
logRemovedPost(post, source, "postsrecomm", needToNotifyBlock);
return false;
}
if (friendsrecomm() && (type.equals("user_rec") || type.equals("friends_recomm"))) {
logRemovedPost(post, source, "friendsrecomm", needToShowToast);
logRemovedPost(post, source, "friendsrecomm", needToNotifyBlock);
return false;
}
if (adsgroup() && post.optInt("marked_as_ads") == 1 && !isWhitelistedAd(post)) {
logRemovedPost(post, source, "marked_as_ads", needToShowToast);
return false;
if (adsgroup() && post.optInt("marked_as_ads") == 1 && !post.optBoolean("sponsorpost") && !isWhitelistedAd(post)) {
logRemovedPost(post, source, "marked_as_ads", needToNotifyBlock);
return needToNotifyBlock;
}
if (PostsPreferences.isPostAd(getOwnerId(post), getPostId(post)) && !isWhitelistedAd(post)) {
logRemovedPost(post, source, "sponsorpost", needToShowToast);
return false;
if (PostsPreferences.isPostAd(getOwnerId(post), getPostId(post)) && !post.optBoolean("sponsorpost") && !isWhitelistedAd(post)) {
logRemovedPost(post, source, "sponsorpost", needToNotifyBlock);
PostsPreferences.incrementNumBlockedPosts();
if (PostsPreferences.isEnabledMarking() || needToNotifyBlock) {
addSponsorPostMark(post);
return true;
} else {
return false;
}
}
if (sponsorFilters(post.optString("text")) && !isWhitelistedFilters(post)) {
logRemovedPost(post, source, "sponsorpost filter", needToShowToast);
return false;
if (VotesPreferences.isPostAd(getOwnerId(post), getPostId(post)) && !post.optBoolean("sponsorpost") && !isWhitelistedAd(post)) {
logRemovedPost(post, source, "sponsorpost vote base", needToNotifyBlock);
addSponsorPostMark(post);
return true;
}
if (sponsorFilters(post.optString("text")) && !post.optBoolean("sponsorpost") && !isWhitelistedFilters(post)) {
logRemovedPost(post, source, "sponsorpost filter", needToNotifyBlock);
FiltersPreferences.incrementNumBlockedPosts();
if (FiltersPreferences.isEnabledMarking() || needToNotifyBlock) {
addSponsorPostMark(post);
return true;
} else {
return false;
}
}
if (checkCopyright(post)) {
logRemovedPost(post, source, "copyright filters", needToShowToast);
return false;
logRemovedPost(post, source, "copyright filters", needToNotifyBlock);
FiltersPreferences.incrementNumBlockedPosts();
return needToNotifyBlock;
}
if (checkCaption(post)) {
logRemovedPost(post, source, "caption filters", needToShowToast);
return false;
logRemovedPost(post, source, "caption filters", needToNotifyBlock);
return needToNotifyBlock;
}
if (hasMiniAppAds(post) && !isWhitelistedFilters(post)) {
logRemovedPost(post, source, "miniapps ban", needToShowToast);
return false;
logRemovedPost(post, source, "miniapps ban", needToNotifyBlock);
return needToNotifyBlock;
}
if (NewsFeedFiltersUtils.injectFiltersReposts(post) && !isWhitelistedFilters(post)) {
logRemovedPost(post, source, "repost ad", needToShowToast);
return false;
logRemovedPost(post, source, "repost ad", needToNotifyBlock);
return needToNotifyBlock;
}
return true;
}
private static void addSponsorPostMark(JSONObject post) throws JSONException {
post.putOpt("marked_as_ads", 1);
post.putOpt("sponsorpost", true);
}
public static JSONArray feedInject(JSONArray items, boolean needToShowToast) {
if (items.length() == 0) return items;
@@ -276,24 +303,26 @@ public class NewsFeedFiltersUtils {
public static boolean sponsorFilters(String text) {
String textInLowerCase = text.toLowerCase();
Set<String> list = filters.stream()
.filter(adword -> !adword.isEmpty())
.collect(Collectors.toSet());
if (!list.isEmpty()) {
return list.stream().anyMatch(adword -> {
if (textInLowerCase.contains(adword.toLowerCase())) {
return filters.stream()
.map(String::toLowerCase)
.filter(adword -> !adword.isEmpty() && textInLowerCase.contains(adword))
.peek(adword -> {
if (dev()) {
Log.d("NewsfeedAdBlockV2", text);
Log.d("NewsfeedAdBlockV2", "Block word: " + adword);
}
return true;
}
return false;
});
}
})
.findAny()
.isPresent();
}
return false;
public static String sponsorFiltersBanWord(String text) {
String textInLowerCase = text.toLowerCase();
return filters.stream()
.map(String::toLowerCase)
.filter(adword -> !adword.isEmpty() && textInLowerCase.contains(adword))
.findAny()
.orElse(null);
}
@@ -0,0 +1,20 @@
package ru.vtosters.sponsorpost.internal;
import androidx.annotation.Nullable;
public class Native {
public static boolean canVote;
public static boolean canVote() {
return canVote;
}
@Nullable
public static native String sig();
@Nullable
public static native String time();
@Nullable
public static native String pkey();
}
@@ -0,0 +1,104 @@
package ru.vtosters.sponsorpost.internal;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class VotesPreferences {
private static final SharedPreferences preferences;
static {
preferences = getPreferences();
}
private static SharedPreferences getPreferences() {
return AndroidUtils.getGlobalContext().getSharedPreferences("ru.vtosters.sponsorpost.votes.prefs", Context.MODE_PRIVATE);
}
public static boolean isEnabled() {
return preferences.getBoolean("sponsortesting", false) && !Preferences.serverFeaturesDisable();
}
public static void setEnabled(boolean status) {
preferences.edit().putBoolean("sponsortesting", status).apply();
}
public static void setUserToken(String token) {
preferences.edit()
.putString("token", token)
.apply();
}
public static void remUserToken() {
preferences.edit()
.remove("token")
.apply();
}
public static String getUserToken() {
return preferences.getString("token", "");
}
public static void savePosts(List<String> posts) {
preferences.edit()
.putStringSet("posts", new HashSet<>(posts))
.apply();
}
public static void saveGroupsIds(List<String> ids) {
preferences.edit()
.putStringSet("groupIds", new HashSet<>(ids))
.apply();
}
public static void saveGroupSpecifiedPosts(List<String> postsIds, Long ownerId) {
Set<String> stringSet = postsIds.stream()
.map(post -> ownerId + "_" + post)
.collect(Collectors.toSet());
preferences.edit()
.putStringSet(ownerId + "_posts", stringSet)
.apply();
}
public static void saveAdPostInfo(long ownerId, long postId) {
Set<String> currentStringSet = preferences.getStringSet("posts", new HashSet<>());
Set<String> newStringSet = new HashSet<>(currentStringSet);
newStringSet.add(ownerId + "_" + postId);
Log.d("SponsorPost", ownerId + "_" + postId);
preferences.edit()
.putStringSet("posts", newStringSet)
.apply();
}
public static boolean isGroupAd(long ownerId) {
Set<String> isGroupAd = preferences.getStringSet("groupIds", new HashSet<>());
if (!isGroupAd.isEmpty() && isEnabled()) {
return isGroupAd.contains(Long.toString(ownerId));
} else {
return false;
}
}
public static boolean isPostAd(long ownerId, long postId) {
if (isEnabled() && isGroupAd(ownerId)) {
Set<String> hasSpecifiedGroupPosts = preferences.getStringSet(ownerId + "_posts", new HashSet<>());
Set<String> hasPost = preferences.getStringSet("posts", new HashSet<>());
return hasPost.contains(ownerId + "_" + postId) || hasSpecifiedGroupPosts.contains(ownerId + "_" + postId);
} else {
return false;
}
}
}
@@ -0,0 +1,153 @@
package ru.vtosters.sponsorpost.internal;
import android.net.Uri;
import android.util.Log;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.sponsorpost.utils.ApiUtils;
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class VotesService {
private static final OkHttpClient client = new OkHttpClient();
private static final String apiPath = ApiUtils.getURL() + "/votes";
private static final String userPath = ApiUtils.getURL() + "/users";
public static Map<String, List<String>> getPosts(Long date) {
String getFromDate = date > 0 ? ("&date=" + date) : "";
Request request = new Request.a()
.b(apiPath + "/getAllPostIds" + "?needProd=true" + getFromDate)
.a("Content-Type", "application/json")
.a("Accept-Encoding", "gzip")
.a();
try (Response response = client.a(request).execute()) {
if (response.h()) {
return parseJSON(new JSONObject(GzipDecompressor.decompressResponse(response)));
} else {
throw new RuntimeException("Failed to get posts: " + response.l());
}
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
}
private static Map<String, List<String>> parseJSON(JSONObject object) throws JSONException {
String PROD_KEY = "prod";
String VOTE_KEY = "vote";
JSONArray prodBase = object.getJSONArray(PROD_KEY);
JSONArray testBase = object.getJSONArray(VOTE_KEY);
if (prodBase.length() == 0 && testBase.length() == 0) {
return Collections.emptyMap();
}
List<String> prodList = IntStream.range(0, prodBase.length())
.mapToObj(prodBase::optString)
.filter(Objects::nonNull)
.collect(Collectors.toList());
List<String> voteList = IntStream.range(0, testBase.length())
.mapToObj(testBase::optString)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return Map.of(PROD_KEY, prodList, VOTE_KEY, voteList);
}
public static Map<String, List<String>> getPostIdsByOwnerId(Long ownerId, Long date) {
String getFromDate = date > 0 ? ("&date=" + date) : "";
String requestUrl = apiPath + "/getPostIdsByOwnerId" + "?ownerId=" + ownerId + getFromDate + "&needProd=true";
Request request = new Request.a()
.b(requestUrl)
.a("Content-Type", "application/json")
.a("Accept-Encoding", "gzip")
.a();
try (Response response = client.a(request).execute()) {
if (response.h()) {
return parseJSON(new JSONObject(GzipDecompressor.decompressResponse(response)));
} else {
throw new RuntimeException("Failed to get postIdsByOwnerId: " + response.l());
}
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
}
public static Map<String, List<String>> getOwnerIds() {
String requestUrl = apiPath + "/getOwnerIds" + "?needProd=true";
Request request = new Request.a()
.b(requestUrl)
.a("Content-Type", "application/json")
.a("Accept-Encoding", "gzip")
.a();
try (Response response = client.a(request).execute()) {
if (response.h()) {
return parseJSON(new JSONObject(GzipDecompressor.decompressResponse(response)));
} else {
throw new RuntimeException("Failed to get ownersIds: " + response.l());
}
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
}
public static JSONObject ratePost(int ownerId, int postId, int date, boolean isAd) {
String token = VotesPreferences.getUserToken() == null || VotesPreferences.getUserToken().isEmpty() ? getVoteToken() : VotesPreferences.getUserToken();
String requestUrl = apiPath + (isAd ? "/dislike" : "/like") + "?ownerId=" + ownerId + "&postId=" + postId+ "&postDate=" + date + "&token=" + Uri.encode(token);
Request request = new Request.a()
.b(requestUrl)
.a("Content-Type", "application/json")
.a();
try (Response response = client.a(request).execute()) {
return new JSONObject(GzipDecompressor.decompressResponse(response));
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
}
public static String getVoteToken() {
String requestUrl = userPath + "/getUserToken" + "?private_key=" + Uri.encode(Native.pkey());
Request request = new Request.a()
.b(requestUrl)
.a("Content-Type", "application/json")
.a("Timestamp", Native.time())
.a("Signature", Native.sig())
.a();
try (Response response = client.a(request).execute()) {
String userToken = new JSONObject(GzipDecompressor.decompressResponse(response)).getString("token");
VotesPreferences.setUserToken(userToken);
return userToken;
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
}
public static boolean isPostAd(long ownerId, long postId) {
String requestUrl = apiPath + "/isPostAdvert?post=" + ownerId + "_" + postId + "&needProd=true";
Request request = new Request.a()
.b(requestUrl)
.a("Content-Type", "application/json")
.a();
try (Response response = client.a(request).execute()) {
if (response.h()) {
return Boolean.parseBoolean(GzipDecompressor.decompressResponse(response));
} else {
throw new RuntimeException("Failed to check post for ad status: " + response.l());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -42,20 +42,7 @@ public class FilterService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
String resp;
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body
resp = GzipDecompressor.decompress(response.a().b());
} else {
// Retrieve the response body directly
resp = response.a().g();
}
// Parse the response body as a JSONArray and extract filter objects
return parseJSON(new JSONArray(resp));
return parseJSON(new JSONArray(GzipDecompressor.decompressResponse(response)));
} else {
if (!FiltersPreferences.getAllFilterIds().isEmpty()) {
return FiltersPreferences.getAllDownloadedFilters(); // check for offline server to get local filters
@@ -75,11 +62,12 @@ public class FilterService {
public static Set<String> downloadFilter(String link) {
Request request = new Request.a()
.b(link)
.a("Accept-Encoding", "gzip")
.a();
try (Response response = client.a(request).execute()) {
if (response.h()) {
return stringToSet(response.a().g());
return stringToSet(GzipDecompressor.decompressResponse(response));
} else {
throw new RuntimeException("Failed to download filter: " + response.l());
}
@@ -39,17 +39,7 @@ public class PostService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
// Handle GZIP-compressed responses
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body and parse JSON data
return parseJSONPosts(new JSONArray(GzipDecompressor.decompress(response.a().b())));
} else {
// Parse JSON data from the response body
return parseJSONPosts(new JSONArray(response.a().g()));
}
return parseJSONPosts(new JSONArray(GzipDecompressor.decompressResponse(response)));
} else {
// Handle error response
throw new RuntimeException("Failed to get posts: " + response.l());
@@ -78,20 +68,7 @@ public class PostService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
String resp;
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body
resp = GzipDecompressor.decompress(response.a().b());
} else {
// Retrieve the response body directly
resp = response.a().g();
}
// Extract the boolean value indicating whether the post is an ad
return Boolean.parseBoolean(resp);
return Boolean.parseBoolean(GzipDecompressor.decompressResponse(response));
} else {
// Handle error response
throw new RuntimeException("Failed to check post for ad status: " + response.l());
@@ -101,10 +78,19 @@ public class PostService {
}
}
public static List<String> getAllPostsIds() {
public static List<String> getAllPostsIds(Long date) {
String getFromDate;
// Get posts older than the specified date (if positive) or all posts (if zero or negative or empty)
if (date > 0) {
getFromDate = ("?date=" + date);
} else {
// No date parameter for retrieving all posts
getFromDate = "";
}
// Create a GET request for retrieving all post IDs
Request request = new Request.a()
.b(apiPath + "/getAllPostIds")
.b(apiPath + "/getAllPostIds" + getFromDate)
.a("Content-Type", "application/json")
.a("Accept-Encoding", "gzip")
.a();
@@ -112,17 +98,7 @@ public class PostService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
// Handle GZIP-compressed responses
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body and extract the first element as a list of post IDs
return new ArrayList<>(Collections.singleton(GzipDecompressor.decompress(response.a().b())));
} else {
// Extract the first element as a list of post IDs
return new ArrayList<>(Collections.singleton(response.a().g()));
}
return new ArrayList<>(Collections.singleton(GzipDecompressor.decompressResponse(response)));
} else {
// Handle error response
throw new RuntimeException("Failed to get posts IDs: " + response.l());
@@ -137,7 +113,7 @@ public class PostService {
// Retrieve post IDs for a specific owner and date
// If date is positive, get posts older than the specified date
// Otherwise, get all posts for the owner
String getFromDate = date > 0 ? ("?date=" + date) : "";
String getFromDate = date > 0 ? ("&date=" + date) : "";
// Construct the API request URL
String requestUrl = apiPath + "/getPostIdsByOwnerId" + "?ownerId=" + ownerId + getFromDate;
@@ -152,20 +128,7 @@ public class PostService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
String resp;
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body
resp = GzipDecompressor.decompress(response.a().b());
} else {
// Retrieve the response body directly
resp = response.a().g();
}
// Parse the response body as a JSONArray and extract post IDs
return parseJSONIds(new JSONArray(resp));
return parseJSONIds(new JSONArray(GzipDecompressor.decompressResponse(response)));
} else {
// Handle error response
throw new RuntimeException("Failed to get postIdsByOwnerId: " + response.l());
@@ -192,20 +155,7 @@ public class PostService {
try (Response response = client.a(request).execute()) {
// Check if the response is successful
if (response.h()) {
// Determine the response encoding
String encoding = response.a("Content-Encoding");
String resp;
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body
resp = GzipDecompressor.decompress(response.a().b());
} else {
// Retrieve the response body directly
resp = response.a().g();
}
// Parse the response body as a JSONArray and extract owner IDs
return parseJSONIds(new JSONArray(resp));
return parseJSONIds(new JSONArray(GzipDecompressor.decompressResponse(response)));
} else {
// Handle error response
throw new RuntimeException("Failed to get ownersIds: " + response.l());
@@ -31,6 +31,26 @@ public class FiltersPreferences {
return AndroidUtils.getGlobalContext().getSharedPreferences("ru.vtosters.sponsorpost.filters.prefs", Context.MODE_PRIVATE);
}
public static boolean isEnabledMarking() {
return preferences.getBoolean("sponsorpost_filters_marking", false);
}
public static void setEnabledMarking(boolean status) {
preferences.edit().putBoolean("sponsorpost_filters_marking", status).apply();
}
public static int getNumBlockedPosts() {
return preferences.getInt("numBlockedPosts", 0);
}
public static void incrementNumBlockedPosts() {
preferences.edit().putInt("numBlockedPosts", getNumBlockedPosts() + 1).apply();
}
public static void dropNumBlockedPosts() {
preferences.edit().putInt("numBlockedPosts", 0).apply();
}
public static void clearAllCachedLists() {
SharedPreferences.Editor editor = preferences.edit();
@@ -41,6 +61,18 @@ public class FiltersPreferences {
editor.apply();
}
public static void forceUpdateFilters() {
SharedPreferences.Editor editor = preferences.edit();
preferences.getAll().keySet().stream()
.filter(key -> key.endsWith(":" + PREF_KEY_VERSION))
.forEach(key -> editor.putString(key, "0"));
editor.apply();
Updates.updateFilters();
}
public static boolean getSavedKeyValue(int id) {
return getFilter(id) != null;
}
@@ -1,5 +1,8 @@
package ru.vtosters.sponsorpost.utils;
import android.util.Log;
import okhttp3.Response;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -26,4 +29,17 @@ public class GzipDecompressor {
throw new RuntimeException(e);
}
}
public static String decompressResponse(Response response) throws IOException {
String encoding = response.a("Content-Encoding");
if (encoding != null && encoding.equals("gzip")) {
// Decompress the response body
Log.d("GZIP", "true, link " + response.o().g());
return GzipDecompressor.decompress(response.a().b());
} else {
// Retrieve the response body directly
Log.d("GZIP", "false, link " + response.o().g());
return response.a().g();
}
}
}
@@ -3,6 +3,7 @@ package ru.vtosters.sponsorpost.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.sponsorpost.data.Post;
@@ -23,13 +24,21 @@ public class PostsPreferences {
}
public static boolean isEnabled() {
return preferences.getBoolean("sponsorpost", true);
return preferences.getBoolean("sponsorpost", true) && !Preferences.serverFeaturesDisable();
}
public static void setEnabled(boolean status) {
preferences.edit().putBoolean("sponsorpost", status).apply();
}
public static boolean isEnabledMarking() {
return preferences.getBoolean("sponsorpost_marking", false);
}
public static void setEnabledMarking(boolean status) {
preferences.edit().putBoolean("sponsorpost_marking", status).apply();
}
public static void savePosts(List<Post> posts) {
Set<String> stringSet = posts.stream()
.map(post -> post.getOwnerId() + "_" + post.getPostId())
@@ -40,7 +49,13 @@ public class PostsPreferences {
.apply();
}
public static void saveGroupSpecifiedPosts(List<Long> postsIds, Long ownerId) {
public static void savePostsStr(List<String> posts) {
preferences.edit()
.putStringSet("posts", new HashSet<>(posts))
.apply();
}
public static void saveGroupSpecifiedPosts(List<?> postsIds, Long ownerId) {
Set<String> stringSet = postsIds.stream()
.map(post -> ownerId + "_" + post)
.collect(Collectors.toSet());
@@ -50,7 +65,7 @@ public class PostsPreferences {
.apply();
}
public static void saveGroupsIds(List<Long> ids) {
public static void saveGroupsIds(List<?> ids) {
Set<String> stringSet = ids.stream().map(String::valueOf).collect(Collectors.toSet());
preferences.edit()
@@ -81,6 +96,18 @@ public class PostsPreferences {
}
}
public static int getNumBlockedPosts() {
return preferences.getInt("numBlockedPosts", 0);
}
public static void incrementNumBlockedPosts() {
preferences.edit().putInt("numBlockedPosts", getNumBlockedPosts() + 1).apply();
}
public static void dropNumBlockedPosts() {
preferences.edit().putInt("numBlockedPosts", 0).apply();
}
public static boolean isPostAd(long ownerId, long postId) {
if (isEnabled() && isGroupAd(ownerId)) {
Set<String> hasSpecifiedGroupPosts = preferences.getStringSet(ownerId + "_posts", new HashSet<>());
@@ -1,14 +1,17 @@
package ru.vtosters.sponsorpost.utils;
import ru.vtosters.hooks.other.Preferences;
import android.util.Log;
import ru.vtosters.lite.utils.NetworkUtils;
import ru.vtosters.lite.utils.newsfeed.NewsFeedFiltersUtils;
import ru.vtosters.sponsorpost.data.Filter;
import ru.vtosters.sponsorpost.data.Post;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.internal.VotesService;
import ru.vtosters.sponsorpost.services.FilterService;
import ru.vtosters.sponsorpost.services.PostService;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -29,14 +32,17 @@ public class Updates {
for (Filter filter : filters) {
Filter localFilter = findLocalFilterById(localFilters, filter.getId());
if (localFilter == null) {
Log.d("Updates", "local filter is not exist, adding new: " + filter.getId());
FiltersPreferences.saveFilter(filter);
} else {
String remoteVersion = filter.getVersion();
String localVersion = localFilter.getVersion();
if (Objects.equals(remoteVersion, localVersion)) {
FiltersPreferences.saveFilter(filter);
} else {
Log.d("Updates", "saved local filter: " + localFilter.getId());
FiltersPreferences.saveFilter(localFilter);
} else {
Log.d("Updates", "saved remote filter: " + filter.getId());
FiltersPreferences.saveFilter(filter);
}
}
}
@@ -59,16 +65,31 @@ public class Updates {
long weekAgo = currentTime - TimeUnit.DAYS.toMillis(7);
if (PostsPreferences.isEnabled() && NetworkUtils.isNetworkConnected()) {
List<Post> posts = PostService.getPosts(weekAgo);
List<Long> groupIds = PostService.getOwnerIds();
if (VotesPreferences.isEnabled()) {
Map<String, List<String>> posts = VotesService.getPosts(weekAgo / 1000);
Map<String, List<String>> groupIds = VotesService.getOwnerIds();
if (!posts.isEmpty()) {
PostsPreferences.savePosts(posts);
}
if (!posts.isEmpty()) {
PostsPreferences.savePostsStr(posts.get("prod"));
VotesPreferences.savePosts(posts.get("vote"));
}
if (!groupIds.isEmpty()) {
PostsPreferences.saveGroupsIds(groupIds);
if (!groupIds.isEmpty()) {
PostsPreferences.saveGroupsIds(groupIds.get("prod"));
VotesPreferences.saveGroupsIds(groupIds.get("vote"));
}
} else {
List<String> posts = PostService.getAllPostsIds(weekAgo / 1000);
List<Long> groupIds = PostService.getOwnerIds();
if (!posts.isEmpty()) {
PostsPreferences.savePostsStr(posts);
}
if (!groupIds.isEmpty()) {
PostsPreferences.saveGroupsIds(groupIds);
}
}
}
}
}
}
+1
View File
@@ -140,6 +140,7 @@
<uses-permission android:name="com.mgoogle.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="app.revanced.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
<application
+2
View File
@@ -75,6 +75,7 @@
--header_tint: %header_text% !important;
--header_tint_alternate: %header_text% !important;
--vkui--color_icon_accent_themed: %header_text% !important;
--vkui--color_text_accent_themed: %header_text% !important;
}
@@ -139,6 +140,7 @@
--header_tint: %header_text% !important;
--header_tint_alternate: %header_text% !important;
--vkui--color_icon_accent_themed: %header_text% !important;
--vkui--color_text_accent_themed: %header_text% !important;
--color_field_border: %field_border% !important;
--vkui--color_field_border: %field_border% !important;
--color_field_background: %field_background% !important;
Binary file not shown.

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