コンテンツへスキップ

LINEグループ参加者の最後の活動日を一覧化する

円滑なグループ運営において、活動していない参加者を退会させたい場合があります。
最後に活動した日付を取得できるツールを探しましたが、見当たらなかったので自作してみました。
グループのトーク履歴をもとに最後に活動した日付を一覧化しています。

ただし、トーク履歴に残るのは「メッセージの送信」「スタンプの利用」「写真・動画・ファイルの添付」「通話の開始」「ノートの投稿」であり、グループ通話に途中から参加してきた人はトーク履歴に残らないので注意が必要です。

手順

  1. スマホからトーク履歴(*.txt)を出力し、パソコンに送ります。パソコン版のLINEでもトーク履歴は取得できますが、遡れる日数に限りがあるのでスマホから出力した方がいいです。
  2. Pythonを公式サイトからパソコンにインストールします。
  3. Windows terminalかCommand Promptを開き、pandasをインストールします。
pip install pandas
  1. .pyファイルを作り、メモ帳か何かで開いて下に記載しているコードを貼り付けて保存します。
  2. .pyファイルをダブルクリックして実行すると、ファイル選択画面が表示されるのでトーク履歴のファイルを選択します。
  3. 実行が完了したら以下のcsvファイルとtxtファイルが出力されます。
    ・トーク履歴を分析しやすい状態に加工したcsvファイル
    ・参加者ごとの最終活動日一覧が記載されているtxtファイル(降順、メモ帳で開くと対応していない記号/顔文字が文字化けするのでVSCode等で開いて中身をテキストコピーするとよいです)

ソースコード

import re
import csv
import datetime
import os
from tkinter import filedialog
import pandas as pd


class Data:
    def __init__(self, year, month, day, hour, minute, person, payload, flag):
        self.year = year
        self.month = month
        self.day = day
        self.hour = hour
        self.minute = minute
        self.person = person
        self.payload = payload
        self.flag = flag


# Set regular expression format
date_pattern = r"20\d{2}/\d{1,2}/\d{1,2}\((月|火|水|木|金|土|日)\)"
message_pattern = r"\d{1,2}:\d{2}\t.*\t.*"
photo_pattern = r"\d{1,2}:\d{2}\t.*\t\[写真]"
sticker_pattern = r"\d{1,2}:\d{2}\t.*\t\[スタンプ]"
video_pattern = r"\d{1,2}:\d{2}\t.*\t\[動画]"
file_pattern = r"\d{1,2}:\d{2}\t.*\t\[ファイル]"
album_build_pattern = r"\d{1,2}:\d{2}\t.*\t\アルバムを作成しました"
note_pattern = r"\d{1,2}:\d{2}\t.*\t.*\がノートに投稿しました"
call_start_pattern = r"\d{1,2}:\d{2}\t.*\t\グループ音声通話が開始されました"
call_end_pattern = r"\d{1,2}:\d{2}\t.*\t\グループ通話が終了しました"
exit_pattern = r"\d{1,2}:\d{2}\t.*\が退出しました"

# Get file path
file_type = [("テキストファイル", "*.txt")]
current_path = os.getcwd()
file_path = filedialog.askopenfilename(filetypes=file_type, initialdir=current_path)

date_ = datetime.datetime.now()
logs = []
exit_list = {}

# Load file
with open(file_path, "r", encoding="utf-8") as f:
    log_text = f.read()

# Read file
for i, log in enumerate(log_text.splitlines()[3:]):
    print(f"{log} : ", end="")
    if log == "":
        print("no data")
        continue
    if re.match(date_pattern, log):
        print("day data")
        date_stamp = log.replace("/", ",")[: log.find("(")]
        date_ = datetime.datetime.strptime(date_stamp, "%Y,%m,%d")
    elif re.match(photo_pattern, log):
        print("photo data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                "",
                "photo",
            )
        )
    elif re.match(video_pattern, log):
        print("video data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                "",
                "video",
            )
        )
    elif re.match(sticker_pattern, log):
        print("sticker data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                "",
                "sticker",
            )
        )
    elif re.match(call_start_pattern, log):
        print("call start data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                "",
                "call start",
            )
        )
    elif re.match(call_end_pattern, log):
        print("call end data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                "",
                "call end",
            )
        )
    elif re.match(file_pattern, log):
        print("file data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                "",
                "",
                "file",
            )
        )
    elif re.match(album_build_pattern, log):
        print("create album data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                "",
                "",
                "album create",
            )
        )
    elif re.match(note_pattern, log):
        print("note data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                "",
                "",
                "note",
            )
        )
    elif re.match(message_pattern, log):
        print("message data")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1],
                splited_log[2],
                "message",
            )
        )
    # tabがない場合=メッセージが改行されて続いている場合
    elif len(re.split("\t", log)) == 1:
        splited_log = re.split("\t", log)
        print("returned data")
        logs[-1].payload += log
    elif re.match(exit_pattern, log):
        print("exit")
        splited_log = re.split("\t", log)
        logs.append(
            Data(
                date_.year,
                date_.month,
                date_.day,
                splited_log[0][0:2],
                splited_log[0][3:5],
                splited_log[1][: splited_log[1].find("が退出")],
                "",
                "exit",
            )
        )
        exit_list[splited_log[1][: splited_log[1].find("が退出")]] = 0
    else:
        pass
        # print("\nNo classified data\n")

# Save as csv
with open("line.csv", "w", encoding="utf-8", newline="") as f:
    for content in logs:
        writer = csv.writer(f)
        writer.writerow(
            [
                str(content.year),
                str(content.month),
                str(content.day),
                str(content.hour),
                str(content.minute),
                str(content.person),
                str(content.payload),
                str(content.flag),
            ]
        )

dic = {}
for content in logs:
    if str(content.person) not in exit_list:
        date_str = (
            str(content.year)
            + str(f"{int(content.month):02}")
            + str(f"{int(content.day):02}")
        )
        dic[str(content.person)] = datetime.datetime.strptime(date_str, "%Y%m%d")

df = pd.DataFrame(list(dic.items()), columns=["Name", "Date"])
df.sort_values(by="Date", ascending=False, inplace=True)
df.to_csv("date.txt", encoding="utf_8_sig", index=False)