├── 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 | --------------------------------------------------------------------------------