Integrate Tellows Spam Score Check with Callscripts

BastyJuice

Bronze Partner
Joined
Oct 1, 2024
Messages
4
Reaction score
1
Hello,

i spend some Time to make A script that fires up on Call on a Trunk. The Goal is Trigger Tellows Service what is a Service in Germany what is Scoring Phonenumbers and get it Spamscore. If the Score is Higher than 5 it should route the Call to an Destination Number.

But it does not Work and i dont know why. But Honestly i am not very Familiar with 3CX Advanced Featues.

Also my Logging Function is not working i got this idea from from https://www.3cx.com/community/threads/how-to-write-logs-at-external-script.54757/

C#:
using CallFlow;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
using TCX.Configuration;
using TCX.PBXAPI;

namespace dummy
{
    public class CallHandler : ScriptBase<CallHandler>
    {
        // Variables for Tellows API and spam forwarding
        private static string tellowsApiKey = "your_api_key";         // API key for Tellows
        private static string tellowsPartnerID = "your_partner_id";   // Partner ID for Tellows
        private static int tellowsThreshold = 7;                      // Threshold for spam detection (1-10)
        private static string spamDestinationNumber = "+1234567890";  // Destination number for spam calls

        PhoneSystem ps = null;

        public CallHandler()
        {
            ps = PhoneSystem.Root;

            if (ps == null)
            {
                Log("Error: Phone system not initialized");
            }
        }

        // Method for logging messages
        public static void Log(string text)
        {
            string fileName = "/var/lib/3cxpbx/Instance1/Data/Logs/tellows.log";

            string logMessage = String.Format("[{0}]: {1}", DateTime.Now.ToString(), text + Environment.NewLine);

            File.AppendAllText(fileName, logMessage);
        }

        // Example method to process an incoming call
        public async Task HandleIncomingCall(ICall myCall)
        {
            try
            {
                // Log the CallerID and check if it is valid
                string callerID = myCall.Caller?.CallerID;

                if (string.IsNullOrEmpty(callerID))
                {
                    Log("Error: CallerID is empty or invalid.");
                    return;
                }

                Log($"Incoming call from: {callerID}");

                // Use Tellows API to analyze the caller
                int spamScore = await GetSpamScore(callerID);

                // If the spam score is equal to or greater than the threshold, mark it as spam
                if (spamScore >= tellowsThreshold)
                {
                    Log($"Spam detected! Score: {spamScore}. Forwarding to spam destination.");
                    await RouteCallToSpamDestination(myCall);
                }
                else
                {
                    Log($"No spam detected. Score: {spamScore}. Processing call normally.");
                }
            }
            catch (Exception ex)
            {
                Log($"Error during call processing: {ex.Message}");
            }
        }

        // Method to fetch the spam score from Tellows (XML Parsing)
        private async Task<int> GetSpamScore(string callerID)
        {
            try
            {
                // Build the Tellows API URL with the relevant parameters
                string apiUrl = $"https://www.tellows.de/basic/num/{callerID}?xml=1&partner={tellowsPartnerID}&apikey={tellowsApiKey}";

                Log($"Making API request to Tellows: {apiUrl}");

                using (HttpClient client = new HttpClient())
                {
                    Log($"Sending API request to Tellows: {apiUrl}");
                    HttpResponseMessage response = await client.GetAsync(apiUrl);
                    Log($"API response status: {response.StatusCode}");
                    response.EnsureSuccessStatusCode();

                    string responseBody = await response.Content.ReadAsStringAsync();
                    Log($"Tellows API response: {responseBody}");

                    // Load the XML data and extract the spam score
                    XDocument xmlResponse = XDocument.Parse(responseBody);

                    // Extract the spam score from the XML response
                    XElement scoreElement = xmlResponse.Root.Element("score");

                    if (scoreElement != null)
                    {
                        int spamScore = int.Parse(scoreElement.Value);
                        Log($"Spam score from Tellows for {callerID}: {spamScore}");
                        return spamScore;
                    }
                    else
                    {
                        Log($"Spam score not found in response for {callerID}");
                        return 0; // Default to no spam if no score found
                    }
                }
            }
            catch (HttpRequestException httpEx)
            {
                Log($"HTTP error during spam score retrieval: {httpEx.Message}");
                return 0; // Default value if API request fails
            }
            catch (Exception ex)
            {
                Log($"General error during spam score retrieval: {ex.Message}");
                return 0; // Default value if an error occurs
            }
        }

        // Method to forward spam calls
        private async Task RouteCallToSpamDestination(ICall myCall)
        {
            try
            {
                // Convert the destination number into a DN object
                DN spamDestination = PhoneSystem.Root.GetDNByNumber(spamDestinationNumber);

                if (spamDestination == null)
                {
                    Log("Spam destination number could not be resolved.");
                    return;
                }

                // Use the ReplaceWithAsync method to forward the call
                CallControlResult result = await myCall.ReplaceWithAsync(new DestinationStruct(spamDestination));

                if (result != null)  // Check if the result is not null
                {
                    Log("Call successfully forwarded to spam destination.");
                }
                else
                {
                    Log("Failed to forward call to spam destination.");
                }
            }
            catch (Exception ex)
            {
                Log($"Error during call forwarding: {ex.Message}");
            }
        }
    }
}
 
Last edited:
o played a little bit arround and get it half working, spam check is working but the script faild to route on spam true to the number it the continues 3cx normal. On SPAM False it cancels the call with EndCall :-( Would be very nice if some one have clou

C++:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
using TCX.Configuration;
using TCX.PBXAPI;

namespace dummy
{
    public class CallHandler : ScriptBase<CallHandler>
    {
        // Tellows API variables
        private static string tellowsApiKey = "your_api_key";         // API key for Tellows
        private static string tellowsPartnerID = "your_partner_id";   // Partner ID for Tellows
        private static int tellowsThreshold = 5;                        // Spam threshold
        private static string spamDestinationNumber = "+4912345678";  // Destination number for spam calls
        private static string logFileName = "/var/lib/3cxpbx/Instance1/Data/Logs/tellows.log";  // Log file path

        PhoneSystem ps = null;

        public override async void Start()
        {
            // Start Logging
            Log("Starting the CallHandler script.");

            await Task.Run(async () =>
            {
                ps = MyCall.PS as PhoneSystem;

                if (ps == null)
                {
                    Log("Error: Phone system not initialized.");
                    return;  // Stop if the phone system is not initialized
                }

                string callerID = MyCall.Caller?.CallerID;

                if (string.IsNullOrEmpty(callerID))
                {
                    Log("Error: CallerID is empty or invalid. Ending call.");
                    MyCall.Return(false);  // End the call if CallerID is invalid
                    return;
                }

                Log($"Incoming call from: {callerID}");

                // Analyze call with Tellows API
                int spamScore = await GetSpamScore(callerID);

                if (spamScore >= tellowsThreshold)
                {
                    Log($"Spam detected with score: {spamScore}. Forwarding to spam destination.");
                    await ReplaceCallWithExternal(spamDestinationNumber);  // Forward call to spam destination
                    MyCall.Return(true);  // End the call after forwarding to spam
                }
                else
                {
                    Log($"No spam detected. Score: {spamScore}. Letting 3CX handle the call.");
                    return;  // Do nothing for non-spam calls, just let 3CX handle it
                }

                Log("Script execution completed.");
            });
        }

        // Method to get spam score from Tellows API
        private async Task<int> GetSpamScore(string callerID)
        {
            try
            {
                string apiUrl = $"https://www.tellows.de/basic/num/{callerID}?xml=1&partner={tellowsPartnerID}&apikey={tellowsApiKey}";
                Log($"Making API request to Tellows: {apiUrl}");

                using (HttpClient client = new HttpClient())
                {
                    HttpResponseMessage response = await client.GetAsync(apiUrl);
                    Log($"API response status: {response.StatusCode}");
                    response.EnsureSuccessStatusCode();

                    string responseBody = await response.Content.ReadAsStringAsync();
                    Log($"Tellows API response: {responseBody}");

                    XDocument xmlResponse = XDocument.Parse(responseBody);
                    XElement scoreElement = xmlResponse.Root.Element("score");

                    if (scoreElement != null)
                    {
                        int spamScore = int.Parse(scoreElement.Value);
                        Log($"Spam score for {callerID}: {spamScore}");
                        return spamScore;
                    }
                    else
                    {
                        Log($"No spam score found for {callerID}.");
                        return 0;  // Default to 0 (non-spam) if no score found
                    }
                }
            }
            catch (Exception ex)
            {
                Log($"Error during spam score retrieval: {ex.Message}");
                return 0;  // Default to 0 (non-spam) if an error occurs
            }
        }

        // Method to forward the call to the spam destination (spam case)
        private async Task ReplaceCallWithExternal(string externalNumber)
        {
            try
            {
                Log($"Attempting to forward call to spam destination: {externalNumber}");

                DestinationStruct destination = new DestinationStruct(DestinationType.External, null, externalNumber);

                CallControlResult result = await MyCall.ReplaceWithAsync(destination);

                if (result != null)
                {
                    Log($"Call successfully forwarded to spam destination: {externalNumber}");
                }
                else
                {
                    Log($"Failed to forward call to spam destination: {externalNumber}");
                }
            }
            catch (Exception ex)
            {
                Log($"Error during call forwarding to spam destination: {ex.Message}");
            }
        }

        // Method for logging messages with a 24-hour time format
        public static void Log(string text)
        {
            string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]: {text}{Environment.NewLine}";
            try
            {
                // Using StreamWriter to ensure immediate writing to the log file
                using (StreamWriter sw = new StreamWriter(logFileName, true))
                {
                    sw.Write(logMessage);
                    sw.Flush();  // Ensure that the log is immediately written to the file
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to log message: {ex.Message}");
            }
        }
    }
}
 
Maybe using the CFD for this would be easier. If you just need to execute an HTTP request, analyze the response and transfer the call depending on that, it's pretty simple with a CFD app.
 
  • Like
Reactions: Evolute IT
yes but that mean if have to go over a dial or somthing this should be avalaibe for the trunk itself
 
The C# script generated by the CFD app can be copied and installed here, it's exactly the same format.
 
  • Like
Reactions: fxbastler
If someone wanna have it, here it goes. Works with latest V20. Some Features are Added fully Woking. You Can Route Spam Calls on an Extension, this Extension setuped like any 3CX Call Group Ring Group or what you want.

https://github.com/BastyJuice/3cx_tellows_script
 
Last edited:
Thanks for sharing it! :)
 

Forum statistics

Threads
112,276
Messages
591,636
Members
165,393
Latest member
Carlos - EC Mates