1+ package com.addev.listaspam
2+
3+ import android.Manifest
4+ import android.app.Activity
5+ import android.app.NotificationChannel
6+ import android.app.NotificationManager
7+ import android.content.Context
8+ import android.content.pm.PackageManager
9+ import android.os.Build
10+ import android.os.Handler
11+ import android.os.Looper
12+ import android.widget.Toast
13+ import androidx.core.app.ActivityCompat
14+ import androidx.core.app.NotificationCompat
15+ import androidx.core.app.NotificationManagerCompat
16+ import okhttp3.Call
17+ import okhttp3.Callback
18+ import okhttp3.OkHttpClient
19+ import okhttp3.Request
20+ import okhttp3.Response
21+ import org.jsoup.Jsoup
22+ import java.io.IOException
23+
24+ class SpamUtils {
25+
26+ companion object {
27+ private const val SPAM_PREFS = " SPAM_PREFS"
28+ private const val BLOCK_NUMBERS_KEY = " BLOCK_NUMBERS"
29+ private const val SPAM_URL_TEMPLATE = " https://www.listaspam.com/busca.php?Telefono=%s"
30+ private const val RESPONDERONO_URL_TEMPLATE = " https://www.responderono.es/numero-de-telefono/%s"
31+ private const val NOTIFICATION_CHANNEL_ID = " NOTIFICATION_CHANNEL"
32+ private const val NOTIFICATION_ID = 1
33+ }
34+
35+ fun checkSpamNumber (context : Context , number : String , callback : (isSpam: Boolean ) -> Unit ) {
36+ val url = SPAM_URL_TEMPLATE .format(number)
37+ val request = Request .Builder ().url(url).build()
38+
39+ OkHttpClient ().newCall(request).enqueue(object : Callback {
40+ override fun onFailure (call : Call , e : IOException ) {
41+ // Handle error gracefully
42+ Handler (Looper .getMainLooper()).post {
43+ showToast(context, " Failed to check number in www.listaspam.com" , Toast .LENGTH_LONG )
44+ callback(false )
45+ }
46+ }
47+
48+ override fun onResponse (call : Call , response : Response ) {
49+ response.body?.string()?.let { body ->
50+ val spamData = parseHtmlForSpamReports(body)
51+ if (spamData.reports > 1 ) {
52+ saveSpamNumber(context, number)
53+ sendNotification(context, number)
54+ callback(true )
55+ } else {
56+ checkResponderono(context, number) { isResponderONoNegative ->
57+ if (isResponderONoNegative) {
58+ saveSpamNumber(context, number)
59+ sendNotification(context, number)
60+ callback(true )
61+ } else {
62+ Handler (Looper .getMainLooper()).post {
63+ showToast(context, " Incoming call is not spam" , Toast .LENGTH_LONG )
64+ }
65+ removeSpamNumber(context, number)
66+ callback(false )
67+ }
68+ }
69+ }
70+ }
71+ }
72+ })
73+ }
74+
75+ fun checkResponderono (context : Context , number : String , callback : (isNegative: Boolean ) -> Unit ) {
76+ val url = RESPONDERONO_URL_TEMPLATE .format(number)
77+ val request = Request .Builder ().url(url).build()
78+
79+ OkHttpClient ().newCall(request).enqueue(object : Callback {
80+ override fun onFailure (call : Call , e : IOException ) {
81+ // Handle error gracefully
82+ Handler (Looper .getMainLooper()).post {
83+ showToast(context, " Failed to check number in www.responderono.es" , Toast .LENGTH_LONG )
84+ callback(false )
85+ }
86+ }
87+
88+ override fun onResponse (call : Call , response : Response ) {
89+ response.body?.string()?.let { body ->
90+ val isResponderONoNegative = body.contains(" .scoreContainer .score.negative" )
91+ callback(isResponderONoNegative)
92+ }
93+ }
94+ })
95+ }
96+
97+ fun saveSpamNumber (context : Context , number : String ) {
98+ val sharedPreferences = context.getSharedPreferences(SPAM_PREFS , Context .MODE_PRIVATE )
99+ val blockedNumbers = sharedPreferences.getStringSet(BLOCK_NUMBERS_KEY , mutableSetOf ())?.toMutableSet()
100+ blockedNumbers?.add(number)
101+ with (sharedPreferences.edit()) {
102+ putStringSet(BLOCK_NUMBERS_KEY , blockedNumbers)
103+ apply ()
104+ }
105+ }
106+
107+ fun removeSpamNumber (context : Context , number : String ) {
108+ val sharedPreferences = context.getSharedPreferences(SPAM_PREFS , Context .MODE_PRIVATE )
109+ val blockedNumbers = sharedPreferences.getStringSet(BLOCK_NUMBERS_KEY , mutableSetOf ())?.toMutableSet()
110+ blockedNumbers?.remove(number)
111+ with (sharedPreferences.edit()) {
112+ putStringSet(BLOCK_NUMBERS_KEY , blockedNumbers)
113+ apply ()
114+ }
115+ }
116+
117+ fun showToast (context : Context , message : String , duration : Int = Toast .LENGTH_SHORT ) {
118+ Toast .makeText(context, message, duration).show()
119+ }
120+
121+ fun sendNotification (context : Context , number : String ) {
122+ createNotificationChannel(context)
123+ val notification = NotificationCompat .Builder (context, NOTIFICATION_CHANNEL_ID )
124+ .setSmallIcon(R .mipmap.ic_launcher)
125+ .setContentTitle(" Spam Number Blocked" )
126+ .setContentText(" Blocked spam number: $number " )
127+ .setPriority(NotificationCompat .PRIORITY_HIGH )
128+ .build()
129+
130+ if (ActivityCompat .checkSelfPermission(
131+ context,
132+ Manifest .permission.POST_NOTIFICATIONS
133+ ) != PackageManager .PERMISSION_GRANTED
134+ ) {
135+ return
136+ }
137+
138+ NotificationManagerCompat .from(context).notify(NOTIFICATION_ID , notification)
139+ }
140+
141+ private fun createNotificationChannel (context : Context ) {
142+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
143+ val name = " Spam Blocker Channel"
144+ val descriptionText = " Notifications for blocked spam numbers"
145+ val importance = NotificationManager .IMPORTANCE_HIGH
146+ val channel = NotificationChannel (NOTIFICATION_CHANNEL_ID , name, importance).apply {
147+ description = descriptionText
148+ }
149+
150+ val notificationManager: NotificationManager =
151+ context.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
152+ notificationManager.createNotificationChannel(channel)
153+ }
154+ }
155+
156+ fun parseHtmlForSpamReports (html : String ): SpamData {
157+ val document = Jsoup .parse(html)
158+ val elementReports = document.select(" .n_reports .result" ).first()
159+ val elementSearches = document.select(" .n_search .result" ).first()
160+
161+ val reports = elementReports?.text()?.toIntOrNull() ? : 0
162+ val searches = elementSearches?.text()?.toIntOrNull() ? : 0
163+
164+ return SpamData (reports, searches, false )
165+ }
166+ }
0 commit comments