├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── themes.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ └── ic_launcher.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── raw │ │ │ │ ├── hide_stories.js │ │ │ │ ├── pinch_to_zoom.js │ │ │ │ ├── hide_groups.js │ │ │ │ ├── hide_pymk.js │ │ │ │ ├── hide_reels.js │ │ │ │ ├── hide_suggested.js │ │ │ │ ├── amoled_black.js │ │ │ │ ├── adblock.js │ │ │ │ ├── sticky_navbar.js │ │ │ │ ├── scripts.js │ │ │ │ └── download_content.js │ │ │ ├── values-zh-rTW │ │ │ │ └── strings.xml │ │ │ ├── values-ar │ │ │ │ └── strings.xml │ │ │ ├── values-bn │ │ │ │ └── strings.xml │ │ │ ├── values-pt │ │ │ │ └── strings.xml │ │ │ ├── values-de │ │ │ │ └── strings.xml │ │ │ ├── values-es │ │ │ │ └── strings.xml │ │ │ └── values-fr │ │ │ │ └── strings.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── ycngmn │ │ │ │ └── nobook │ │ │ │ ├── ui │ │ │ │ ├── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ ├── components │ │ │ │ │ ├── NetworkErrorDialog.kt │ │ │ │ │ └── settings │ │ │ │ │ │ ├── SettingsDialog.kt │ │ │ │ │ │ ├── SettingsItem.kt │ │ │ │ │ │ └── SettingsContent.kt │ │ │ │ └── screens │ │ │ │ │ ├── SplashLoading.kt │ │ │ │ │ └── NobookWV.kt │ │ │ │ ├── utils │ │ │ │ ├── desktopUserAgent.kt │ │ │ │ ├── jsBridge │ │ │ │ │ ├── NobookSettings.kt │ │ │ │ │ ├── ThemeChange.kt │ │ │ │ │ └── DownloadBridge.kt │ │ │ │ ├── isAutoDesktop.kt │ │ │ │ ├── ExternalRequestInterceptor.kt │ │ │ │ ├── fetchScripts.kt │ │ │ │ ├── fbRedirectSanitizer.kt │ │ │ │ ├── rememberImeHeight.kt │ │ │ │ └── FilePicker.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainNavigation.kt │ │ │ │ ├── NobookDataStore.kt │ │ │ │ └── NobookViewModel.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── ycngmn │ │ │ └── nobook │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── ycngmn │ │ └── nobook │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── images ├── get-it-on-github.png └── nobook_github_cover.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml └── workflows │ └── create-release.yml ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /images/get-it-on-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/images/get-it-on-github.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/nobook_github_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/images/nobook_github_cover.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycngmn/Nobook/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #09547A 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: 💬 Ask a Question 5 | url: https://github.com/ycngmn/Nobook/discussions 6 | about: Ask general questions or get help 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val GoogleDark = Color(0xFF121212) 6 | val FacebookBlue = Color(0xFF1877F2) 7 | val FacebookDark = Color(0XFF232425) -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/desktopUserAgent.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils 2 | 3 | fun getDesktopUserAgent(): String { 4 | return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0" 5 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 16 13:00:47 CEST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/jsBridge/NobookSettings.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils.jsBridge 2 | 3 | import android.webkit.JavascriptInterface 4 | 5 | class NobookSettings ( 6 | private val toggleSettings: () -> Unit, 7 | ) { 8 | @JavascriptInterface 9 | fun onSettingsToggle() = toggleSettings() 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /app/release 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | 17 | # Android build outputs 18 | /build/ 19 | /app/build/ 20 | *.apk 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/test/java/com/ycngmn/nobook/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/jsBridge/ThemeChange.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils.jsBridge 2 | 3 | import android.webkit.JavascriptInterface 4 | import androidx.core.graphics.toColorInt 5 | 6 | class ThemeChange( 7 | private val setThemeColor: (Int) -> Unit 8 | ) { 9 | @JavascriptInterface 10 | fun onThemeColorChanged(newColor: String?) { 11 | if (newColor.isNullOrBlank()) return 12 | runCatching { setThemeColor(newColor.toColorInt()) } 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/isAutoDesktop.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.platform.LocalConfiguration 6 | 7 | @Composable 8 | fun isAutoDesktop(): Boolean { 9 | val configuration = LocalConfiguration.current 10 | return if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { 11 | return true 12 | } else configuration.smallestScreenWidthDp >= 600 13 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.core.view.WindowCompat 8 | 9 | class MainActivity : ComponentActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | WindowCompat.setDecorFitsSystemWindows(window, false) 12 | enableEdgeToEdge() 13 | super.onCreate(savedInstanceState) 14 | setContent { MainNavigation(intent?.data) } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/hide_stories.js: -------------------------------------------------------------------------------- 1 | // Hide "story" container from feed. 2 | (function() { 3 | const hideTargetElement = () => { 4 | const storyContainer = isDesktopMode() ? document.querySelector('.x193iq5w.xgmub6v.x1ceravr') 5 | : document.querySelector('[data-mcomponent="MContainer"][data-srat="43"]'); 6 | 7 | if (storyContainer) storyContainer.style.display = 'none'; 8 | }; 9 | 10 | hideTargetElement(); 11 | 12 | const observer = new MutationObserver(() => { 13 | hideTargetElement(); 14 | }); 15 | 16 | observer.observe(document.body, { 17 | childList: true, 18 | subtree: true 19 | }); 20 | })(); 21 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "Nobook" 23 | include(":app") 24 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ycngmn/nobook/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.ycngmn.nobook", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontwarn org.slf4j.impl.StaticLoggerBinder -------------------------------------------------------------------------------- /app/src/main/res/raw/pinch_to_zoom.js: -------------------------------------------------------------------------------- 1 | // Zoom Disable Script 2 | (function() { 3 | function applyViewportLock() { 4 | let viewport = document.querySelector('meta[name="viewport"]'); 5 | if (!viewport) { 6 | viewport = document.createElement('meta'); 7 | viewport.name = "viewport"; 8 | document.head.appendChild(viewport); 9 | } 10 | // allow zooming on photos. 11 | if (!window.location.href.includes("facebook.com/photo.php")) 12 | viewport.content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"; 13 | } 14 | 15 | applyViewportLock(); 16 | 17 | const observer = new MutationObserver(() => { 18 | applyViewportLock(); 19 | }); 20 | 21 | observer.observe(document.head || document.documentElement, { 22 | childList: true, 23 | subtree: true 24 | }); 25 | })(); -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/MainNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook 2 | 3 | import android.net.Uri 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.collectAsState 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.graphics.toArgb 8 | import androidx.core.graphics.ColorUtils 9 | import androidx.lifecycle.viewmodel.compose.viewModel 10 | import com.ycngmn.nobook.ui.screens.NobookWebView 11 | import com.ycngmn.nobook.ui.theme.NobookTheme 12 | 13 | @Composable 14 | fun MainNavigation(data: Uri?) { 15 | 16 | val viewModel: NobookViewModel = viewModel() 17 | val themeColor = viewModel.themeColor.collectAsState().value 18 | val isDarkTheme = remember(themeColor) { 19 | ColorUtils.calculateLuminance( 20 | themeColor.toArgb() 21 | ) < 0.5 22 | } 23 | 24 | NobookTheme(darkTheme = isDarkTheme) { 25 | NobookWebView( 26 | data?.toString() ?: "https://facebook.com/", 27 | viewModel = viewModel 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/ExternalRequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils 2 | 3 | import com.multiplatform.webview.request.RequestInterceptor 4 | import com.multiplatform.webview.request.WebRequest 5 | import com.multiplatform.webview.request.WebRequestInterceptResult 6 | import com.multiplatform.webview.web.WebViewNavigator 7 | 8 | class ExternalRequestInterceptor( 9 | private val handleExternalUrl: (String) -> Unit 10 | ) : RequestInterceptor { 11 | 12 | override fun onInterceptUrlRequest( 13 | request: WebRequest, 14 | navigator: WebViewNavigator 15 | ): WebRequestInterceptResult { 16 | 17 | val internalUrlRegex = Regex( 18 | """https?://(?!(?:l|lm)\.)[^/]*(?:facebook|messenger)\.com/.*""" 19 | ) 20 | return if (internalUrlRegex.containsMatchIn(request.url) && request.isForMainFrame) { 21 | WebRequestInterceptResult.Allow 22 | } else { 23 | handleExternalUrl(fbRedirectSanitizer(request.url)) 24 | WebRequestInterceptResult.Reject 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/ycngmn/nobook/utils/fetchScripts.kt: -------------------------------------------------------------------------------- 1 | package com.ycngmn.nobook.utils 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.engine.okhttp.OkHttp 6 | import io.ktor.client.request.get 7 | import io.ktor.http.HttpStatusCode 8 | 9 | 10 | const val SCRIPT_SRC = "https://raw.githubusercontent.com/ycngmn/Nobook/refs/heads/main/app/src/main/res/raw/" 11 | 12 | data class Script( 13 | val condition: Boolean, 14 | val scriptRes: Int, 15 | val scriptTitle: String 16 | ) 17 | 18 | suspend fun fetchScripts( 19 | scripts: List