about summary refs log tree commit diff stats
path: root/lib/Lacerta/CLI.rakumod
blob: 3ce42b0142703dc36d78027fd70948cb0c3db8d8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use Terminal::Spinners;
use Text::Table::Simple;

#| parses WhatsApp export
unit sub MAIN (
    Str $input where *.IO.f = "input", #= input log file to parse
    Str $profile-name = "Andinus", #= your WhatsApp profile name
);

#| Parses the WhatsApp logs.
grammar WhatsApp {
    token TOP { <Text> || <Notice> }

    token Notice { <date> ', ' <time> ' - ' <message> }
    token Text { <date> ', ' <time> ' - ' <name> ': ' <message> }

    token date { (\d+) ** 3 % '/' }
    token time { (\d+) ** 2 % ':' }
    token name { [[\w|\s]+ | '+' \d+ [\d+|\s]+] }
    token number { '+' \d+ [\d+|\s]+ }
    token message { .* }
}

my WhatsApp @logs;

{
    Spinner.new(:type<bounce2>).await: Promise.start: {
        @logs = $input.IO.lines.race.map({WhatsApp.parse($_)}).grep(so *);
    }
    put "Parsed {@logs.elems} logs in " ~ (now - ENTER now) ~ "s";
}

{
    my List @data;
    my List @given-data;
    my List @most-spoken-data;
    my Int $no-of-spoken = 8;

    Spinner.new(:type<bounce>).await: Promise.start: {
        my Promise @promises;
        for @logs.grep(*<Text>).map(*<Text>).map(*<name>.Str).unique -> $name {
            next if $name eq "ERROR";
            push @promises, start with @logs.grep(*<Text>).map(*<Text>).grep(*<name> eq $name) {
                @data.push(
                    (
                        $name,
                        .elems,
                        .map(*<message>.words).sum,
                        .grep(*<message> eq ($name eq $profile-name
                                             ?? "You deleted this message"
                                             !! "This message was deleted")).elems,
                        .grep(*<message> eq "<Media omitted>").elems,
                        .map(*<time>[0][0].Int).Bag.max(*.values).key,
                        @logs.grep(*<Notice>).grep(
                            *<Notice><message>
                            eq ($name eq $profile-name ?? "You left" !! "$name left")
                        ).elems,
                    )
                );
                with .map(*<message>).map(*.lc).cache {
                    @given-data.push(
                        (
                            $name,
                            .grep(*.contains: "fuck").elems,
                        )
                    );
                    @most-spoken-data.push(
                        (
                            $name,
                            .grep(* ne "<media omitted>")
                             .grep(* ne ($name eq $profile-name
                                         ?? "you deleted this message"
                                         !! "this message was deleted")
                                  ).map(*.words).Bag.grep(*.key.chars >= 4)
                             .sort(*.values).reverse.map({"{$_.key} ({$_.value})"}).head($no-of-spoken).Slip,
                        )
                    );
                }
            }
        }
        await @promises;
    }
    put "Generated data in " ~ (now - ENTER now) ~ "s" ~ "\n";

    my List %options = headers => (corner_marker => "*", bottom_border => "-");
    .say for lol2table(<Name Messages Words Deleted Media ActiveHour Left>, @data, |%options);
    .say for lol2table(<Name FucksGiven>, @given-data, |%options);
    .say for lol2table((|<Name MostSpoken-#1>, |("#" X~ (2..$no-of-spoken))), @most-spoken-data, |%options);
}