Scraping various information from LINE notifications

Introduction

This is the 9th year since the LINE service started on June 23, 2011, but since the number of monthly active users in Japan is about 87 million, most people are familiar with LINE. think. I think that I usually communicate mainly with LINE, but I thought that if I could get data from LINE, which I use frequently, I could use it in various ways, so I wondered if I could somehow get information about the exchange.

I use this method to publish an app called "Pickup Notification Sound" on Google Play that allows LINE notification sounds and ringtones to sound separately! https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/e48b6416-da86-2db1-0c9a-0c1a5c4be66e.png https://play.google.com/store/apps/details?id=rabbitp.sns.notifisort https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/a4ce1b87-24e8-d6ae-c0ac-1e4ed07cf6f4.png Google Play and the Google Play logo are trademarks of Google LLC.

To explain how to do it roughly ...

You will receive a message on LINE ↓ Notification is displayed ↓ Get notifications using Android's "Access Notifications" feature ↓ Scrap only the information you need

It is like this. I will show you how to do it later. (If you want to skip the introduction, click [here](# table of contents of scraping method))

Before scraping ... "How to get notifications"

It is convenient to use it because there is a reference project. Click here → https://github.com/oggata/NotificationListenerServiceDemo If you open Android Studio after downloading, it will look like this: 000261.png Since it is a project made in 2015, a warning will be displayed because the Gradle version is old. Open File menu → Project Structure ..., change the Android Gradle plugin version and Gradle version to whatever you like and click OK. 000262.png (I used plugin 4.0.0 / Gradle 6.1.1.)

After clicking OK, if you see a screen like this, change the two parts of "repositories" as follows. 000263.png

build.gradle


buildscript {
    repositories {
        google()
        jcenter()
    }
    
    ...abridgement

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://jitpack.io"
        }
    }
}

When the fix is complete, click Retry in the upper left and wait for the sync to finish.

Then the error will appear again, so delete "targetSdkVersion 21" in build.gradle, click the link "Remove minSdkVersion and sync project", and delete the following description. 000264.png

AndroidManifest.xml


<uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="18" />

...Other parts omitted

After removing it, open build.gradle again and click "Sync Now" in the upper right corner. This should clear all the errors.

This project is to "display the raw data as an overlay flowing from the top of the screen when a notification comes", but it is a little inconvenient to use for confirmation, so we will improve it.

Add a button to open the "Access Notification" settings screen

Place a button in activity_main.xml and add the following program.

MainActivity.java


        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Notification permission screen
                Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
                startActivity(intent);
            }
        });

Stop overlaying and display in EditText

I thought it would be nice to be able to copy, so this time I will use EditText instead of TextView. Place EditText on the full screen of activity_main.xml and add the following program.

MainActivity.java


public class MainActivity extends Activity {
    EditText editText;
    String log = "";

...abridgement

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = (EditText) findViewById(R.id.editText);

...abridgement

    class NotificationReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            String msg = intent.getStringExtra("notification_msg") + "\n";
            
            final DateFormat df = new SimpleDateFormat("MM/dd HH:mm:ss");
            final Date date = new Date(System.currentTimeMillis());
            log=df.format(date)+"\n"+msg+"----------------\n"+log;

            editText.setText(log);
        }
    }
}

The new notification will be displayed on top.

Add a reset button

Add a button to reset the log and display. Place a button in activity_main.xml and add the following program.

MainActivity.java


        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //reset
                log="";
                editText.setText("");
            }
        });

Get detailed information about notifications

You can also use "sbn.getNotification (). Extras" to get information that is not actually displayed in the notification. You can get the sender name, the full text of the message, etc. from this.

NLService.java


...abridgement

            i.putExtra("notification_msg", ""
                    + "ID :" + sbn.getId() + "\n"
                    + "Txt :" + sbn.getNotification().tickerText + "\n"
                    + "Pkg :" + sbn.getPackageName() + "\n"
                    + "Tim :" + sbn.getPostTime() + "\n"
                    + "extras :" + sbn.getNotification().extras+"\n"
                    + "\n");

...abridgement

App execution screen

There are some changes in the layout etc. https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/6d2f3141-695c-2efc-b2e8-9764cc7f5ad2.png

Table of contents of scraping method

For the sake of clarity, we have organized each piece of information you want to scrape.

[Discrimination]
LINE notification
message or free call
[For messages]
sender name
target group name
message content
URL of the sent LINE stamp
Sender's profile image
chat ID
Marked or when notification is deleted
[For free calls]
Start calling
Incoming call
missed call
Call start
End call

About the countStringInString function

Part of the program uses the countStringInString function, which counts the number of times a character appears.

    //Get the number of character appearances
    public static int countStringInString(String target, String searchWord) {
        //return (target.length() - target.replaceAll(searchWord, "").length()) / searchWord.length();

        Pattern p = Pattern.compile(searchWord);
        Matcher m = p.matcher(target);
        String result = m.replaceAll("");

        int out = target.length() - result.length();
        return out;
    }

Discrimination

Whether it is a LINE notification

Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if(sbn.getPackageName().equals("jp.naver.line.android")) {
            //Describe here what you want to do when you receive a LINE notification
            
        }

Message or free call

Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("880002") != -1 || String.valueOf(sbn.getId()).equals("110000")) {
             //For missed calls or notification IDs for free calls → Free calls
             
        } else {
             //If not → message
             
        }

Description location: In "onNotificationRemoved" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).equals("110000")) {
             //When the free call is rejected or the call end button is pressed and the call ends → Free call
             
        }

For messages

Sender name

You can get the name of the person who sent it. It can be obtained for both individual and group cases. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        //Plastic surgery
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //Delete 8 characters from the beginning "Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,Number of

        //Extract the sender name
        String Name="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("android.title=")==0){
                //At the beginning "android.title=Came → Sender name
                Name = extars[a].substring(14);  //Delete characters from the beginning "android.title=」
                break;
            }
        }
        if(Name.indexOf(" - ")!=-1){
            //In Android 5/6 series, Name is "[Sender name] - [group name]"become
            Name=Name.split(" - ")[0];
        }
        //Name: Sender name

Target group name

You can get the name of the group to which the message was sent. Null is returned if it was an individual rather than a group. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        //Plastic surgery
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //Delete 8 characters from the beginning "Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,Number of
        
        //Extract the group name
        String GroupName="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("android.title=")==0){
                //At the beginning "android.title=Came → Sender name
                GroupName = extars[a].substring(14);  //Delete characters from the beginning "android.title=」
                break;
            }
        }
        if(GroupName.indexOf(" - ")!=-1){
            //In Android 5/6 series, Name is "[Sender name] - [group name]"become
            GroupName=Name.split(" - ")[1];
        }else {
            GroupName=sbn.getNotification().extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
            if(GroupName!=null){
                Name=Name.substring(GroupName.length()+2);  //At the beginning ":Is given, so delete it
            }
        }
        //GroupName: Group name

Message content

You can get the sent message. Even if "..." is displayed in the notification and omitted, the full text can be obtained. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        //Extract the contents
        String Text = sbn.getNotification().extras.getString(Notification.EXTRA_TEXT);
        //Text: Content

URL of the sent LINE stamp

URLs that start with " https://stickershop.line-scdn.net/products/ " and end with ".png " Can be obtained. It is also possible to display the stamp image by HTTP GET based on this URL. (How to display image by HTTP GET: https://akira-watson.com/android/httpurlconnection-get.html) If it is not a stamp, null is returned. By the way, the image size varies depending on the stamp, but it is about 120px or more. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        //Extract the image URL of the stamp
        String StampURL="null";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf(" line.sticker.url=")==0){
                //"Line" at the beginning.sticker.url=”→ Stamp image URL
                StampURL = extars[a].substring(18);  //Delete 18 characters from the beginning "line".sticker.url=」
                break;
            }
        }
        //StampURL: Stamp image URL

Sender's profile picture

Since it can be obtained as a Bitmap type, it can be displayed in ImageView or saved as PNG. The image size is 108px x 108px. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        Bitmap LargeIcon = drawableToBitmap(sbn.getNotification().getLargeIcon().loadDrawable(NLService.this));

...abridgement

    //Convert Drawable type to Bitmap type
    public static Bitmap drawableToBitmap (Drawable drawable) {
        Bitmap bitmap = null;

        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if(bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }

        if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

Chat ID

An identifier that is fixed for each friend or group. You can use it to determine if you have friends or groups with the same name. Perhaps LINE's "Create Talk Shortcut" feature is a URL scheme that uses this ID, but I couldn't find it when I searched ...

NLService.java


        //Plastic surgery
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //Delete 8 characters from the beginning "Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,Number of
        
        //Extract chat ID
        String ChatID="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("line.chat.id=")==0){
                //"Line" at the beginning.chat.id=Came → Chat ID
                ChatID = extars[a].substring(13);  //Delete 13 characters from the beginning "line".chat.id=」
                break;
            }
        }
        //ChatID: Chat ID

Marked as read or when the notification is deleted

Called when you open the LINE talk screen and mark it as read, or delete the message notification. Description location: In "onNotificationRemoved" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("880000") != -1) {  //Either 15880000 or 16880000?
            //Marked as read or when the notification is deleted
            
        }

For free calls

The process is called in the following flow.

Start calling

Called when you make a call. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("Outgoing") != -1) {  //Either 15880002 or 1688002?
            //Start calling
            
        }

Incoming call

Called when you receive a call. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("Incoming call") != -1) {  //Either 15880002 or 1688002?
            //Incoming call
            
        }

Missed call

Called when there is an incoming call and the other party hangs up (presses the reject button or does not answer). Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("880002") != -1 && Name.equals("LINE missed call")) {  //Either 15880002 or 1688002?
            //Missed call
            
        }

Start a call

It is called when the call is started. Description location: In "onNotificationPosted" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("talking") != -1) {  //Either 15880002 or 1688002?
            //Start a call
            
        }

End call

Called whenever the call ends in some way. Description location: In "onNotificationRemoved" of "NLService.java"

NLService.java


        if (String.valueOf(sbn.getId()).equals("110000")) {
            //End call
            
        }

Caution

This is a method that I found by actually receiving LINE notifications and verifying how to scrape successfully. Please note that there is a possibility that it will not be usable in the future depending on the specification change of LINE. (Actually, there was a major specification change in 2018, and some changes were made in 2020.)

Also, under Android 7.0 (?), There seems to be a problem that "onNotificationPosted" is not called while trying. If you want to use it for verification, please use Android 7.0 or above. We also recommend setting minSdkVersion to 24 (Android 7.0) or higher.

Reference site

Recommended Posts

Scraping various information from LINE notifications