fix(3rd-party Reddit apps): Spoof user agent to work around Reddit API issues (#3253)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
d8415c0bee
commit
495e6d65e7
|
@ -555,6 +555,7 @@ public final class app/revanced/patches/reddit/customclients/baconreader/api/Spo
|
||||||
public final class app/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
public final class app/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
||||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch;
|
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/boostforreddit/api/SpoofClientPatch;
|
||||||
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/infinityforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
public final class app/revanced/patches/reddit/customclients/infinityforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
||||||
|
@ -577,6 +578,11 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/ads/D
|
||||||
public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch : app/revanced/patches/reddit/customclients/BaseSpoofClientPatch {
|
||||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch;
|
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/api/SpoofClientPatch;
|
||||||
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/reddit/customclients/joeyforreddit/api/fingerprints/AuthUtilityUserAgent : app/revanced/patcher/fingerprint/MethodFingerprint {
|
||||||
|
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/joeyforreddit/api/fingerprints/AuthUtilityUserAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/joeyforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {
|
public final class app/revanced/patches/reddit/customclients/joeyforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||||
|
@ -621,6 +627,7 @@ public final class app/revanced/patches/reddit/customclients/syncforreddit/api/S
|
||||||
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/api/SpoofClientPatch;
|
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/api/SpoofClientPatch;
|
||||||
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
public fun patchClientId (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
public fun patchMiscellaneous (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
public fun patchMiscellaneous (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
public fun patchUserAgent (Ljava/util/Set;Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/reddit/customclients/syncforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {
|
public final class app/revanced/patches/reddit/customclients/syncforreddit/detection/piracy/DisablePiracyDetectionPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||||
|
|
|
@ -5,15 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
||||||
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
||||||
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.GetClientIdFingerprint
|
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.GetClientIdFingerprint
|
||||||
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.LoginActivityOnCreateFingerprint
|
import app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints.JRAWUserAgent
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object SpoofClientPatch : BaseSpoofClientPatch(
|
object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
redirectUri = "http://rubenmayayo.com",
|
redirectUri = "http://rubenmayayo.com",
|
||||||
clientIdFingerprints = setOf(GetClientIdFingerprint),
|
clientIdFingerprints = setOf(GetClientIdFingerprint),
|
||||||
userAgentFingerprints = setOf(LoginActivityOnCreateFingerprint),
|
userAgentFingerprints = setOf(JRAWUserAgent),
|
||||||
compatiblePackages = setOf(CompatiblePackage("com.rubenmayayo.reddit"))
|
compatiblePackages = setOf(CompatiblePackage("com.rubenmayayo.reddit")),
|
||||||
) {
|
) {
|
||||||
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
|
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
|
||||||
first().mutableMethod.addInstructions(
|
first().mutableMethod.addInstructions(
|
||||||
|
@ -21,7 +20,17 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
"""
|
"""
|
||||||
const-string v0, "$clientId"
|
const-string v0, "$clientId"
|
||||||
return-object v0
|
return-object v0
|
||||||
"""
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
|
||||||
|
// Use a random user agent.
|
||||||
|
val randomName = (0..100000).random()
|
||||||
|
|
||||||
|
first().mutableMethod.addInstructions(
|
||||||
|
1,
|
||||||
|
"const-string v3, \"$randomName\"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
|
||||||
|
internal object JRAWUserAgent : MethodFingerprint(
|
||||||
|
strings = listOf("platform", "appId", "version", "redditUsername"),
|
||||||
|
)
|
|
@ -1,14 +0,0 @@
|
||||||
package app.revanced.patches.reddit.customclients.boostforreddit.api.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
|
|
||||||
internal object LoginActivityOnCreateFingerprint : MethodFingerprint(
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.CONST_4
|
|
||||||
),
|
|
||||||
customFingerprint = { method, classDef ->
|
|
||||||
method.name == "onCreate" && classDef.type.endsWith("LoginActivity;")
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -2,8 +2,10 @@ package app.revanced.patches.reddit.customclients.joeyforreddit.api
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
||||||
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
||||||
|
import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.AuthUtilityUserAgent
|
||||||
import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.GetClientIdFingerprint
|
import app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints.GetClientIdFingerprint
|
||||||
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.DisablePiracyDetectionPatch
|
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.DisablePiracyDetectionPatch
|
||||||
|
|
||||||
|
@ -12,6 +14,7 @@ import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.
|
||||||
object SpoofClientPatch : BaseSpoofClientPatch(
|
object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
redirectUri = "https://127.0.0.1:65023/authorize_callback",
|
redirectUri = "https://127.0.0.1:65023/authorize_callback",
|
||||||
clientIdFingerprints = setOf(GetClientIdFingerprint),
|
clientIdFingerprints = setOf(GetClientIdFingerprint),
|
||||||
|
userAgentFingerprints = setOf(AuthUtilityUserAgent),
|
||||||
compatiblePackages = setOf(
|
compatiblePackages = setOf(
|
||||||
CompatiblePackage("o.o.joey"),
|
CompatiblePackage("o.o.joey"),
|
||||||
CompatiblePackage("o.o.joey.pro"),
|
CompatiblePackage("o.o.joey.pro"),
|
||||||
|
@ -28,4 +31,18 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
|
||||||
|
// Use a random user agent.
|
||||||
|
val randomName = (0..100000).random()
|
||||||
|
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
|
||||||
|
|
||||||
|
first().mutableMethod.replaceInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
const-string v0, "$userAgent"
|
||||||
|
return-object v0
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package app.revanced.patches.reddit.customclients.joeyforreddit.api.fingerprints
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.or
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
object AuthUtilityUserAgent : MethodFingerprint(
|
||||||
|
returnType = "Ljava/lang/String;",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||||
|
opcodes = listOf(Opcode.APUT_OBJECT),
|
||||||
|
customFingerprint = { _, classDef ->
|
||||||
|
classDef.sourceFile == "AuthUtility.java"
|
||||||
|
},
|
||||||
|
)
|
|
@ -1,18 +1,16 @@
|
||||||
package app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy
|
package app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy
|
||||||
|
|
||||||
import app.revanced.util.exception
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.fingerprints.PiracyDetectionFingerprint
|
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.fingerprints.PiracyDetectionFingerprint
|
||||||
|
import app.revanced.util.exception
|
||||||
|
|
||||||
object DisablePiracyDetectionPatch : BytecodePatch(setOf(PiracyDetectionFingerprint)) {
|
object DisablePiracyDetectionPatch : BytecodePatch(setOf(PiracyDetectionFingerprint)) {
|
||||||
override fun execute(context: BytecodeContext) {
|
override fun execute(context: BytecodeContext) {
|
||||||
PiracyDetectionFingerprint.result?.mutableMethod?.addInstruction(
|
PiracyDetectionFingerprint.result?.mutableMethod?.addInstruction(
|
||||||
0,
|
0,
|
||||||
"""
|
"return-void",
|
||||||
return-void
|
|
||||||
"""
|
|
||||||
) ?: throw PiracyDetectionFingerprint.exception
|
) ?: throw PiracyDetectionFingerprint.exception
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@ import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object SpoofClientPatch : BaseSpoofClientPatch(
|
object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
redirectUri = "redditisfun://auth",
|
redirectUri = "redditisfun://auth",
|
||||||
|
@ -54,7 +53,7 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
|
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
|
||||||
// Use a random user agent.
|
// Use a random user agent.
|
||||||
val randomName = (0..100000).random()
|
val randomName = (0..100000).random()
|
||||||
val userAgent = "android:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
|
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
|
||||||
|
|
||||||
first().mutableMethod.addInstructions(
|
first().mutableMethod.addInstructions(
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -6,10 +6,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
import app.revanced.patcher.fingerprint.MethodFingerprintResult
|
||||||
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
import app.revanced.patches.reddit.customclients.BaseSpoofClientPatch
|
||||||
|
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.*
|
||||||
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetAuthorizationStringFingerprint
|
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetAuthorizationStringFingerprint
|
||||||
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetBearerTokenFingerprint
|
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.GetBearerTokenFingerprint
|
||||||
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.ImgurImageAPIFingerprint
|
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.ImgurImageAPIFingerprint
|
||||||
import app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints.LoadBrowserURLFingerprint
|
|
||||||
import app.revanced.patches.reddit.customclients.syncforreddit.detection.piracy.DisablePiracyDetectionPatch
|
import app.revanced.patches.reddit.customclients.syncforreddit.detection.piracy.DisablePiracyDetectionPatch
|
||||||
import app.revanced.util.exception
|
import app.revanced.util.exception
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
@ -17,19 +17,18 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object SpoofClientPatch : BaseSpoofClientPatch(
|
object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
redirectUri = "http://redditsync/auth",
|
redirectUri = "http://redditsync/auth",
|
||||||
miscellaneousFingerprints = setOf(ImgurImageAPIFingerprint),
|
miscellaneousFingerprints = setOf(ImgurImageAPIFingerprint),
|
||||||
clientIdFingerprints = setOf(GetAuthorizationStringFingerprint),
|
clientIdFingerprints = setOf(GetAuthorizationStringFingerprint),
|
||||||
userAgentFingerprints = setOf(LoadBrowserURLFingerprint),
|
userAgentFingerprints = setOf(GetUserAgentFingerprint),
|
||||||
compatiblePackages = setOf(
|
compatiblePackages = setOf(
|
||||||
CompatiblePackage("com.laurencedawson.reddit_sync"),
|
CompatiblePackage("com.laurencedawson.reddit_sync"),
|
||||||
CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
|
CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
|
||||||
CompatiblePackage("com.laurencedawson.reddit_sync.dev")
|
CompatiblePackage("com.laurencedawson.reddit_sync.dev"),
|
||||||
),
|
),
|
||||||
dependencies = setOf(DisablePiracyDetectionPatch::class)
|
dependencies = setOf(DisablePiracyDetectionPatch::class),
|
||||||
) {
|
) {
|
||||||
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
|
override fun Set<MethodFingerprintResult>.patchClientId(context: BytecodeContext) {
|
||||||
forEach { fingerprintResult ->
|
forEach { fingerprintResult ->
|
||||||
|
@ -41,7 +40,7 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
"""
|
"""
|
||||||
const-string v0, "Basic $auth"
|
const-string v0, "Basic $auth"
|
||||||
return-object v0
|
return-object v0
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
} ?: throw GetBearerTokenFingerprint.exception
|
} ?: throw GetBearerTokenFingerprint.exception
|
||||||
}.let {
|
}.let {
|
||||||
|
@ -54,12 +53,12 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
|
|
||||||
val newAuthorizationUrl = reference.string.replace(
|
val newAuthorizationUrl = reference.string.replace(
|
||||||
"client_id=.*?&".toRegex(),
|
"client_id=.*?&".toRegex(),
|
||||||
"client_id=$clientId&"
|
"client_id=$clientId&",
|
||||||
)
|
)
|
||||||
|
|
||||||
replaceInstruction(
|
replaceInstruction(
|
||||||
occurrenceIndex,
|
occurrenceIndex,
|
||||||
"const-string v$targetRegister, \"$newAuthorizationUrl\""
|
"const-string v$targetRegister, \"$newAuthorizationUrl\"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +71,21 @@ object SpoofClientPatch : BaseSpoofClientPatch(
|
||||||
|
|
||||||
it.mutableMethod.replaceInstruction(
|
it.mutableMethod.replaceInstruction(
|
||||||
apiUrlIndex,
|
apiUrlIndex,
|
||||||
"const-string v1, \"https://api.imgur.com/3/image\""
|
"const-string v1, \"https://api.imgur.com/3/image\"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun Set<MethodFingerprintResult>.patchUserAgent(context: BytecodeContext) {
|
||||||
|
// Use a random user agent.
|
||||||
|
val randomName = (0..100000).random()
|
||||||
|
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
|
||||||
|
|
||||||
|
first().mutableMethod.replaceInstruction(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
const-string v0, "$userAgent"
|
||||||
|
return-object v0
|
||||||
|
""",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package app.revanced.patches.reddit.customclients.syncforreddit.api.fingerprints
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||||
|
|
||||||
|
internal object GetUserAgentFingerprint : MethodFingerprint(
|
||||||
|
strings = listOf("android:com.laurencedawson.reddit_sync"),
|
||||||
|
)
|
Loading…
Reference in a new issue