├── lib
├── __init__.py
├── STOPWORDS.py
├── folder_prep.py
├── choice_maker.py
├── plot.py
└── gui.py
├── .gitmodules
├── screenshots
└── GUI-start.png
├── requirements.txt
├── .gitignore
├── LICENSE
├── README.md
└── chat_miner.py
/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/STOPWORDS.py:
--------------------------------------------------------------------------------
1 | STOPWORDS = []
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "chatminer"]
2 | path = chatminer
3 | url = https://github.com/joweich/chat-miner
4 |
--------------------------------------------------------------------------------
/screenshots/GUI-start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vactomas/chat-miner_GUI/HEAD/screenshots/GUI-start.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | bs4
2 | pandas
3 | numpy
4 | matplotlib
5 | wordcloud
6 | python-dateutil
7 | tqdm
8 | pysimplegui
--------------------------------------------------------------------------------
/lib/folder_prep.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | def folder_prep(
5 | plot_path,
6 | ):
7 |
8 | try:
9 |
10 | os.mkdir(plot_path)
11 |
12 | except FileExistsError:
13 |
14 | return
15 |
16 | return
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | chat-miner
2 | build
3 | dist
4 | plots
5 | chat
6 | chat_miner.spec
7 | lib/__pycache__/__init__.cpython-311.pyc
8 | lib/__pycache__/choice_maker.cpython-311 (conflicted copy 2023-01-07 094347).pyc
9 | lib/__pycache__/choice_maker.cpython-311.pyc
10 | lib/__pycache__/choose_colour.cpython-311.pyc
11 | lib/__pycache__/choose_parser.cpython-311.pyc
12 | lib/__pycache__/folder_prep.cpython-311.pyc
13 | lib/__pycache__/gui.cpython-311 (conflicted copy 2023-01-07 094348).pyc
14 | lib/__pycache__/gui.cpython-311.pyc
15 | lib/__pycache__/plot.cpython-311 (conflicted copy 2023-01-07 095849).pyc
16 | lib/__pycache__/plot.cpython-311.pyc
17 | lib/__pycache__/STOPWORDS.cpython-311.pyc
18 | chatminer/__pycache__/__init__.cpython-311.pyc
19 | chatminer/__pycache__/chatparsers.cpython-311.pyc
20 | chatminer/__pycache__/visualizations.cpython-311.pyc
21 | lib/gui_test.py
22 | lib/main.py
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Tomáš Vacek
4 |
5 | The "chat-miner" library
6 | Copyright (c) 2021 Jonas Weich
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chat-miner GUI
2 |
3 | This is a simple GUI made for the [chat-miner](https://github.com/joweich/chat-miner) library.
4 |
5 | It supports Telegram, Facebook Messenger, WhatsApp, Instagram, and Signal.
6 |
7 | ## 1. Download the release
8 |
9 | Download the latest .exe release from [here](https://github.com/vactomas/chat-miner_GUI/releases)
10 |
11 | ## 2. Download chat exports
12 |
13 | Now you need to download the export files of your chats. Guides can be found here for [WhatsApp](https://faq.whatsapp.com/1180414079177245/), [Signal](https://github.com/carderne/signal-export), [Telegram](https://telegram.org/blog/export-and-more), [Facebook Messenger](https://www.facebook.com/help/messenger-app/713635396288741), and [Instagram Chats](https://help.instagram.com/181231772500920).
14 |
15 | ## 3. Run the executable and make some choices
16 |
17 | After running the executable, this window will appear.
18 |
19 |
20 |
21 |
22 |
23 | Here you can choose:
24 | - the chat export type
25 | - which year should be used for data extraction
26 | - which graphs should be plotted
27 | - the chat export file itself
28 | - colours for different graph types
29 | - graph file name
30 |
31 | Only the export file itself needs to be specified, along with the year. You can leave all the remaining settings as they are.
32 |
33 | ## 4. Press start and enjoy!
34 |
35 | After pressing the start button, the program will automatically create a folder called plots in which, it will store your graphs.
36 |
37 | Enjoy the fun with visualizing your chats and be sure to share them with your friends. 😁
38 |
39 | ## If you are interested in contributing, running from source or building it yourself
40 |
41 | ```
42 | git clone https://github.com/vactomas/chat-miner_GUI
43 | cd chat-miner_GUI
44 | pip install -r requirements.txt
45 | ```
46 |
47 | I use pyinstaller to build the executable.
48 |
49 | ```
50 | pyinstaller --onefile --windowed --add-data 'path-to-your-installed-modules/Lib/site-packages/wordcloud/stopwords;wordcloud/' chat_miner.py
51 | ```
--------------------------------------------------------------------------------
/chat_miner.py:
--------------------------------------------------------------------------------
1 | from lib.folder_prep import folder_prep
2 | from lib.plot import plot
3 | from lib.choice_maker import (
4 | choose_gradient,
5 | choose_color,
6 | choose_parser,
7 | choose_what_to_plot,
8 | )
9 | from lib.gui import make_window
10 |
11 | import PySimpleGUI as sg
12 |
13 |
14 | def main():
15 |
16 | plot_path = "./plots/"
17 |
18 | window = make_window(
19 | sg.theme("DarkAmber"),
20 | )
21 |
22 | while True:
23 | event, values = window.read(
24 | timeout=100,
25 | )
26 |
27 | if event == sg.WIN_CLOSED or event == "Exit":
28 |
29 | break
30 |
31 | if event == "START":
32 |
33 | try:
34 |
35 | folder_prep(plot_path)
36 |
37 | parser = values["-MESSENGER-"]
38 | file = values["-CHAT_EXPORT_FILE-"]
39 | filename = str(values["-GRAPH_FILE_NAME-"])
40 | year = int(values["-YEAR-"])
41 | cmap = choose_gradient(values["-HEATMAP_GRADIENT-"])
42 | color_sunburst = choose_color(values["-SUNBURST_COLOR-"])
43 | color_radarchart = choose_color(values["-RADARCHART_COLOR-"])
44 | heatmap = int(values["-HEATMAP-"])
45 | sunburst = int(values["-SUNBURST-"])
46 | wordcloud = int(values["-WORDCLOUD-"])
47 | radarchart = int(values["-RADARCHART-"])
48 | what_to_plot = choose_what_to_plot(
49 | heatmap,
50 | sunburst,
51 | wordcloud,
52 | radarchart,
53 | )
54 |
55 | parser = choose_parser(
56 | parser,
57 | file,
58 | )
59 |
60 | parser.parse_file_into_df()
61 |
62 | condition_df = parser.df.loc[
63 | (parser.df["datetime"] >= f"{str(year)}-01-01 00:01")
64 | & (parser.df["datetime"] < f"{str(year)}-12-31 23:59")
65 | ]
66 |
67 | if not condition_df.empty:
68 |
69 | plot(
70 | parser,
71 | filename,
72 | plot_path,
73 | year,
74 | cmap,
75 | color_sunburst,
76 | color_radarchart,
77 | what_to_plot,
78 | )
79 |
80 | print("\n\n\n\n[LOG] Done!")
81 |
82 | else:
83 |
84 | print("\n\n\n\n[LOG] Error - invalid year")
85 |
86 | except AssertionError:
87 |
88 | print("\n\n\n\n[LOG] Error! Check everything and try again")
89 |
90 | return
91 |
92 |
93 | if __name__ == "__main__":
94 |
95 | main()
96 |
--------------------------------------------------------------------------------
/lib/choice_maker.py:
--------------------------------------------------------------------------------
1 | from chatminer.chatminer.chatparsers import (
2 | TelegramJsonParser,
3 | FacebookMessengerParser,
4 | SignalParser,
5 | WhatsAppParser,
6 | InstagramJsonParser,
7 | )
8 |
9 |
10 | def choose_parser(
11 | parser,
12 | file,
13 | ):
14 |
15 | match parser:
16 |
17 | case "Telegram - JSON":
18 | parser = TelegramJsonParser(file)
19 |
20 | case "Facebook Messenger":
21 | parser = FacebookMessengerParser(file)
22 |
23 | case "Signal":
24 | parser = SignalParser(file)
25 |
26 | case "WhatsApp":
27 | parser = WhatsAppParser(file)
28 |
29 | case "Instagram":
30 | parser = InstagramJsonParser(file)
31 |
32 | return parser
33 |
34 |
35 | def choose_what_to_plot(
36 | heatmap,
37 | sunburst,
38 | wordcloud,
39 | radarchart,
40 | ):
41 |
42 | what_to_plot = []
43 |
44 | if heatmap == 1:
45 | what_to_plot.append("HEATMAP")
46 |
47 | if sunburst == 1:
48 | what_to_plot.append("SUNBURST")
49 |
50 | if wordcloud == 1:
51 | what_to_plot.append("WORDCLOUD")
52 |
53 | if radarchart == 1:
54 | what_to_plot.append('RADARCHART')
55 |
56 | return what_to_plot
57 |
58 |
59 | def choose_gradient(gradient):
60 |
61 | match gradient:
62 |
63 | case "Greys":
64 | return "Greys"
65 |
66 | case "Purples":
67 | return "Purples"
68 |
69 | case "Blues":
70 | return "Blues"
71 |
72 | case "Greens":
73 | return "Greens"
74 |
75 | case "Oranges":
76 | return "Oranges"
77 |
78 | case "Reds":
79 | return "Reds"
80 |
81 | case "YellowOrangeBrown":
82 | return "YlOrBr"
83 |
84 | case "YellowOrangeRed":
85 | return "YlOrRd"
86 |
87 | case "OrangeRed":
88 | return "OrRd"
89 |
90 | case "PurpleRed":
91 | return "PuRd"
92 |
93 | case "RedPurple":
94 | return "RdPu"
95 |
96 | case "BluePurple":
97 | return "BuPu"
98 |
99 | case "GreenBlue":
100 | return "GnBu"
101 |
102 | case "PurpleBlue":
103 | return "PuBu"
104 |
105 | case "YellowGreenBlue":
106 | return "YlGnBu"
107 |
108 | case "BlueGreen":
109 | return "BuGn"
110 |
111 | case "YellowGreen":
112 | return "YlGn"
113 |
114 | case "Heat":
115 | return "hot"
116 |
117 |
118 | def choose_color(color):
119 |
120 | match color:
121 |
122 | case "Blue":
123 | return "b"
124 |
125 | case "Green":
126 | return "g"
127 |
128 | case "Red":
129 | return "r"
130 |
131 | case "Cyan":
132 | return "c"
133 |
134 | case "Magenta":
135 | return "m"
136 |
137 | case "Yellow":
138 | return "y"
139 |
140 | case "Black":
141 | return "k"
142 |
143 | case "White":
144 | return "w"
145 |
146 | case "Orange":
147 | return "#fa8128"
148 |
149 | case "Pink":
150 | return "#ff69b4"
151 |
--------------------------------------------------------------------------------
/lib/plot.py:
--------------------------------------------------------------------------------
1 | from lib.STOPWORDS import STOPWORDS
2 | import chatminer.chatminer.visualizations as vis
3 | import matplotlib.pyplot as plt
4 |
5 |
6 | def plot_heatmap(
7 | parser,
8 | year,
9 | cmap,
10 | ):
11 |
12 | fig, ax = plt.subplots(figsize=(9, 3))
13 |
14 | ax = vis.calendar_heatmap(
15 | parser.df,
16 | year=year,
17 | linewidth=1,
18 | monthly_border=True,
19 | cmap=cmap,
20 | )
21 |
22 | return fig
23 |
24 |
25 | def plot_sunburst(
26 | parser,
27 | colour,
28 | ):
29 |
30 | fig, ax = plt.subplots(
31 | figsize=(7, 3),
32 | subplot_kw={"projection": "polar"},
33 | )
34 |
35 | ax = vis.sunburst(
36 | parser.df,
37 | highlight_max=True,
38 | # isolines=[400, 800],
39 | isolines_relative=True,
40 | ax=ax,
41 | color=colour,
42 | )
43 |
44 | return fig
45 |
46 |
47 | def plot_wordcloud(
48 | parser,
49 | ):
50 |
51 | fig, ax = plt.subplots(figsize=(8, 3))
52 | kwargs = {
53 | "background_color": "white",
54 | "width": 800,
55 | "height": 300,
56 | "max_words": 500,
57 | }
58 |
59 | ax = vis.wordcloud(
60 | parser.df,
61 | ax=ax,
62 | stopwords=STOPWORDS,
63 | **kwargs,
64 | )
65 |
66 | return fig
67 |
68 |
69 | def plot_radarchart(
70 | parser,
71 | colour,
72 | ):
73 |
74 | vis.radar_factory(
75 | 7,
76 | frame="polygon",
77 | )
78 | vis.radar(
79 | parser.df,
80 | ax=None,
81 | )
82 |
83 | fig, ax = plt.subplots(
84 | figsize=(7, 3),
85 | subplot_kw=dict(projection="radar"),
86 | )
87 |
88 | ax = vis.radar(
89 | parser.df,
90 | ax=ax,
91 | color=colour,
92 | )
93 |
94 | return fig
95 |
96 |
97 | def plot(
98 | parser,
99 | filename,
100 | plot_path,
101 | year,
102 | cmap,
103 | colour_sunburst,
104 | colour_radarchart,
105 | what_to_plot,
106 | ):
107 |
108 | heatmap_fig = ""
109 | sunburst_fig = ""
110 | wordcloud_fig = ""
111 | radarchart_fig = ""
112 |
113 | if "HEATMAP" in what_to_plot:
114 |
115 | heatmap_fig = plot_heatmap(
116 | parser,
117 | year,
118 | cmap,
119 | )
120 |
121 | file = f"{plot_path}/{filename}_calendar.png"
122 | heatmap_fig.savefig(file)
123 |
124 | if "SUNBURST" in what_to_plot:
125 |
126 | sunburst_fig = plot_sunburst(
127 | parser,
128 | colour_sunburst,
129 | )
130 |
131 | file = f"{plot_path}/{filename}_sunburst.png"
132 | sunburst_fig.savefig(file)
133 |
134 | if "WORDCLOUD" in what_to_plot:
135 |
136 | wordcloud_fig = plot_wordcloud(
137 | parser,
138 | )
139 |
140 | file = f"{plot_path}/{filename}_wordcloud.png"
141 | wordcloud_fig.savefig(file)
142 |
143 | if "RADARCHART" in what_to_plot:
144 |
145 | radarchart_fig = plot_radarchart(
146 | parser,
147 | colour_radarchart,
148 | )
149 |
150 | file = f"{plot_path}/{filename}_radarchart.png"
151 |
152 | radarchart_fig.savefig(file)
153 |
154 | return heatmap_fig, sunburst_fig, wordcloud_fig, radarchart_fig
155 |
--------------------------------------------------------------------------------
/lib/gui.py:
--------------------------------------------------------------------------------
1 | import PySimpleGUI as sg
2 | import datetime as dt
3 |
4 |
5 | def make_window(theme):
6 |
7 | sg.theme(theme)
8 |
9 | select_year = [
10 | [
11 | sg.Text("Year to plot:"),
12 | sg.InputText(
13 | key="-YEAR-",
14 | default_text=dt.date.today().year,
15 | s=(10, 2),
16 | ),
17 | ]
18 | ]
19 |
20 | choose_messenger = [
21 | [
22 | sg.OptionMenu(
23 | [
24 | "Telegram - JSON",
25 | "Facebook Messenger",
26 | "Signal",
27 | "WhatsApp",
28 | "Instagram",
29 | ],
30 | s=(20, 2),
31 | default_value="Telegram - JSON",
32 | key="-MESSENGER-",
33 | )
34 | ]
35 | ]
36 |
37 | graphs_to_plot = [
38 | [
39 | sg.Checkbox(
40 | "Heatmap",
41 | default=True,
42 | key="-HEATMAP-",
43 | ),
44 | sg.Checkbox(
45 | "Sunburst",
46 | default=True,
47 | key="-SUNBURST-",
48 | ),
49 | sg.Checkbox(
50 | "Wordcloud",
51 | default=False,
52 | key="-WORDCLOUD-",
53 | ),
54 | sg.Checkbox(
55 | "Radarchart",
56 | default=True,
57 | key="-RADARCHART-",
58 | ),
59 | ],
60 | ]
61 |
62 | file_select = [
63 | [
64 | sg.Text("Choose chat export file:"),
65 | sg.Input(s=(20, 1)),
66 | sg.FileBrowse(key="-CHAT_EXPORT_FILE-"),
67 | ],
68 | ]
69 |
70 | colour_picker_text = [
71 | [
72 | sg.Text("Heatmap colour gradient:"),
73 | ],
74 | [
75 | sg.Text("Sunburst graph colour:"),
76 | ],
77 | [
78 | sg.Text("Radarchart graph colour:"),
79 | ],
80 | ]
81 |
82 | colour_picker = [
83 | [
84 | sg.OptionMenu(
85 | [
86 | "Greys",
87 | "Purples",
88 | "Blues",
89 | "Greens",
90 | "Oranges",
91 | "Reds",
92 | "YellowOrangeBrown",
93 | "YellowOrangeRed",
94 | "OrangeRed",
95 | "PurpleRed",
96 | "RedPurple",
97 | "BluePurple",
98 | "GreenBlue",
99 | "PurpleBlue",
100 | "YellowGreenBlue",
101 | "PurpleBlueGreen",
102 | "BlueGreen",
103 | "YellowGreen",
104 | "Heat",
105 | ],
106 | s=(15, 1),
107 | default_value="Blues",
108 | key="-HEATMAP_GRADIENT-",
109 | ),
110 | ],
111 | [
112 | sg.OptionMenu(
113 | [
114 | "Blue",
115 | "Green",
116 | "Red",
117 | "Cyan",
118 | "Magenta",
119 | "Yellow",
120 | "Black",
121 | "White",
122 | "Orange",
123 | "Pink",
124 | ],
125 | s=(15, 1),
126 | default_value="Blue",
127 | key="-SUNBURST_COLOR-",
128 | ),
129 | ],
130 | [
131 | sg.OptionMenu(
132 | [
133 | "Blue",
134 | "Green",
135 | "Red",
136 | "Cyan",
137 | "Magenta",
138 | "Yellow",
139 | "Black",
140 | "White",
141 | "Orange",
142 | "Pink",
143 | ],
144 | s=(15, 1),
145 | default_value="Blue",
146 | key="-RADARCHART_COLOR-",
147 | ),
148 | ],
149 | ]
150 |
151 | graph_file_name = [
152 | [
153 | sg.Column([[sg.Text("Graph file name:"),]]),
154 | sg.Column([[sg.InputText(
155 | key="-GRAPH_FILE_NAME-",
156 | s=(15, 2),
157 | default_text="export",
158 | ),]])
159 | ]
160 | ]
161 |
162 | start_the_process = [
163 | [
164 | sg.Button("START"),
165 | ]
166 | ]
167 |
168 | status = [
169 | [
170 | sg.Multiline(
171 | size=(40, 2),
172 | expand_x=True,
173 | expand_y=True,
174 | write_only=False,
175 | reroute_stdout=True,
176 | reroute_stderr=True,
177 | echo_stdout_stderr=True,
178 | autoscroll=True,
179 | auto_refresh=True,
180 | ),
181 | ]
182 | ]
183 |
184 | layout = [
185 | [
186 | sg.Column(choose_messenger),
187 | sg.Column(select_year),
188 | ],
189 | [graphs_to_plot],
190 | [file_select],
191 | [
192 | sg.Column(colour_picker_text),
193 | sg.Column(colour_picker),
194 | ],
195 | [graph_file_name],
196 | [
197 | sg.Column(start_the_process),
198 | sg.Column(status),
199 | ],
200 | ]
201 |
202 | window = sg.Window(
203 | title="Chat Miner",
204 | layout=layout,
205 | grab_anywhere=True,
206 | resizable=True,
207 | margins=(0, 0),
208 | use_custom_titlebar=True,
209 | finalize=True,
210 | keep_on_top=True,
211 | )
212 |
213 | return window
214 |
--------------------------------------------------------------------------------