두번째커밋
This commit is contained in:
parent
cc08c1135c
commit
3b4e61f451
Binary file not shown.
22
main.py
22
main.py
|
|
@ -0,0 +1,22 @@
|
||||||
|
import sys
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
from src.chat_ui import MainWindow
|
||||||
|
from src.database import *
|
||||||
|
def main():
|
||||||
|
mongo_config = MongoConfig() # MongoConfig 인스턴스 생성
|
||||||
|
mongo_uri = mongo_config.get_mongo_uri() # MongoDB URI 가져오기
|
||||||
|
|
||||||
|
db_manager = DatabaseManager(mongo_uri) # URI 전달
|
||||||
|
|
||||||
|
if db_manager.connect():
|
||||||
|
print("Database connected successfully.")
|
||||||
|
else:
|
||||||
|
print("Failed to connect to the database.")
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
mainWindow = MainWindow(db_manager) # db_manager 전달
|
||||||
|
mainWindow.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,53 @@
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# import locale
|
||||||
|
# 로케일을 한국어로 설정
|
||||||
|
# locale.setlocale(locale.LC_TIME, 'ko_KR.utf8')
|
||||||
|
|
||||||
|
def parse_chat_log(contents):
|
||||||
|
# 날짜에서 요일 정보를 제외하고 파싱
|
||||||
|
date_pattern = re.compile(r"--------------- (\d{4}년 \d{1,2}월 \d{1,2}일) [가-힣]+ ---------------")
|
||||||
|
dialogue_pattern = re.compile(r"\[(.*?)\] \[(.*?)\] (.*)")
|
||||||
|
|
||||||
|
chatroom_name_pattern = re.compile(r"^(.*?) 님과 카카오톡 대화")
|
||||||
|
lines = contents.split('\n')
|
||||||
|
chatroom_name_match = chatroom_name_pattern.match(lines[0])
|
||||||
|
chatroom_name = chatroom_name_match.group(1) if chatroom_name_match else "Unknown Chatroom"
|
||||||
|
|
||||||
|
parsed_data = []
|
||||||
|
current_date = None
|
||||||
|
|
||||||
|
for line in lines[3:]:
|
||||||
|
if date_match := date_pattern.match(line):
|
||||||
|
# 날짜 파싱 및 datetime 객체 생성
|
||||||
|
# current_date = datetime.strptime(date_match.group(1), "%Y년 %m월 %d일 %A")
|
||||||
|
current_date_str = date_match.group(1)
|
||||||
|
current_date = datetime.strptime(current_date_str, "%Y년 %m월 %d일")
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dialogue_match := dialogue_pattern.match(line):
|
||||||
|
user, time_str, message = dialogue_match.groups()
|
||||||
|
time_str = time_str.replace("오전", "AM").replace("오후", "PM")
|
||||||
|
# 시간 파싱 및 datetime 객체에 시간 추가
|
||||||
|
time = datetime.strptime(time_str, "%p %I:%M").time()
|
||||||
|
full_datetime = datetime.combine(current_date, time)
|
||||||
|
|
||||||
|
parsed_data.append({
|
||||||
|
'chatroom_name': chatroom_name,
|
||||||
|
'datetime': {
|
||||||
|
'year': full_datetime.year,
|
||||||
|
'month': full_datetime.month,
|
||||||
|
'day': full_datetime.day,
|
||||||
|
'weekday': full_datetime.strftime('%A'),
|
||||||
|
'hour': full_datetime.hour,
|
||||||
|
'minute': full_datetime.minute
|
||||||
|
},
|
||||||
|
'user': user,
|
||||||
|
'message': message
|
||||||
|
})
|
||||||
|
elif line.strip() and not line.startswith('['): # 연속된 메시지 처리
|
||||||
|
parsed_data[-1]['message'] += "\n" + line # 마지막 메시지에 추가
|
||||||
|
|
||||||
|
return parsed_data
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
from PyQt5.QtWidgets import *
|
||||||
|
from src.chat_parser import *
|
||||||
|
from src.database import *
|
||||||
|
from PyQt5.QtCore import QDate
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self, db_manager):
|
||||||
|
super().__init__()
|
||||||
|
self.db_manager = db_manager # 데이터베이스 매니저 인스턴스 저장
|
||||||
|
self.initUI()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setWindowTitle('KakaoTalk Chat Parser')
|
||||||
|
self.setGeometry(300, 300, 800, 600)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# 캘린더 및 날짜 선택
|
||||||
|
self.startDateCalendar = QCalendarWidget()
|
||||||
|
self.startDateCalendar.setSelectedDate(QDate(2024, 1, 1))
|
||||||
|
self.endDateCalendar = QCalendarWidget()
|
||||||
|
self.endDateCalendar.setSelectedDate(QDate.currentDate())
|
||||||
|
|
||||||
|
# 채팅 데이터 가져오기 버튼
|
||||||
|
self.loadChatDataButton = QPushButton('채팅 데이터 가져오기')
|
||||||
|
self.loadChatDataButton.clicked.connect(self.load_chat_data)
|
||||||
|
|
||||||
|
# 채팅 파일 입력 버튼
|
||||||
|
self.loadChatFileButton = QPushButton('채팅 파일 입력')
|
||||||
|
self.loadChatFileButton.clicked.connect(self.load_chat_log)
|
||||||
|
|
||||||
|
# 사용자 선택 콤보 박스
|
||||||
|
self.userComboBox = QComboBox()
|
||||||
|
self.userComboBox.addItem("전체 유저 선택")
|
||||||
|
|
||||||
|
# 검색 필드
|
||||||
|
self.searchField = QLineEdit()
|
||||||
|
self.searchButton = QPushButton('검색')
|
||||||
|
self.searchButton.clicked.connect(self.search_chat_data)
|
||||||
|
|
||||||
|
# 배치
|
||||||
|
calendarLayout = QHBoxLayout()
|
||||||
|
calendarLayout.addWidget(self.startDateCalendar)
|
||||||
|
calendarLayout.addWidget(self.endDateCalendar)
|
||||||
|
|
||||||
|
layout.addLayout(calendarLayout)
|
||||||
|
layout.addWidget(self.loadChatDataButton)
|
||||||
|
layout.addWidget(self.userComboBox)
|
||||||
|
layout.addWidget(self.searchField)
|
||||||
|
layout.addWidget(self.searchButton)
|
||||||
|
layout.addWidget(self.loadChatFileButton)
|
||||||
|
|
||||||
|
self.textEdit = QTextEdit()
|
||||||
|
layout.addWidget(self.textEdit)
|
||||||
|
|
||||||
|
central_widget = QWidget()
|
||||||
|
central_widget.setLayout(layout)
|
||||||
|
self.setCentralWidget(central_widget)
|
||||||
|
|
||||||
|
def load_chat_data(self):
|
||||||
|
start_date = self.startDateCalendar.selectedDate().toPyDate()
|
||||||
|
end_date = self.endDateCalendar.selectedDate().toPyDate()
|
||||||
|
collection_name = "YourCollectionNameHere" # 적절한 컬렉션 이름 사용
|
||||||
|
|
||||||
|
documents = self.db_manager.get_documents_by_date_range(collection_name, start_date, end_date)
|
||||||
|
self.textEdit.clear()
|
||||||
|
for doc in documents:
|
||||||
|
self.textEdit.append(f"{doc['datetime']} - {doc['user']}: {doc['message']}")
|
||||||
|
|
||||||
|
self.textEdit.append("Chat logs loaded successfully from the database.")
|
||||||
|
|
||||||
|
def search_chat_data(self):
|
||||||
|
text = self.searchField.text()
|
||||||
|
collection_name = "YourCollectionNameHere" # 적절한 컬렉션 이름 사용
|
||||||
|
|
||||||
|
if text.strip():
|
||||||
|
documents = self.db_manager.search_documents_by_text(collection_name, text)
|
||||||
|
self.textEdit.clear()
|
||||||
|
for doc in documents:
|
||||||
|
self.textEdit.append(f"{doc['datetime']} - {doc['user']}: {doc['message']}")
|
||||||
|
|
||||||
|
self.textEdit.append(f"Search results for '{text}':")
|
||||||
|
else:
|
||||||
|
self.textEdit.append("Please enter text to search.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def load_chat_log(self):
|
||||||
|
options = QFileDialog.Options()
|
||||||
|
filename, _ = QFileDialog.getOpenFileName(self, "채팅파일 입력", "", "Text Files (*.txt);;All Files (*)", options=options)
|
||||||
|
if filename:
|
||||||
|
try:
|
||||||
|
with open(filename, 'r', encoding='utf-8') as file:
|
||||||
|
chat_log_contents = file.read()
|
||||||
|
|
||||||
|
# Parse the chat log contents
|
||||||
|
parsed_data = parse_chat_log(chat_log_contents)
|
||||||
|
|
||||||
|
if self.db_manager.db is not None: # 데이터베이스 연결 확인
|
||||||
|
for data in parsed_data:
|
||||||
|
chatroom_name = data.get('chatroom_name')
|
||||||
|
if self.db_manager.insert_if_not_exists(chatroom_name, data):
|
||||||
|
self.textEdit.append(f"{data.get('user')} - {data.get('message')}.")
|
||||||
|
else:
|
||||||
|
self.textEdit.append(f"Failed to insert data for chatroom {chatroom_name}.")
|
||||||
|
self.textEdit.append("All chat logs loaded and parsed successfully.")
|
||||||
|
else:
|
||||||
|
self.textEdit.append("Database connection not established.")
|
||||||
|
except Exception as e:
|
||||||
|
self.textEdit.append(f"An error occurred: {e}")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[MongoDB]
|
||||||
|
uri=mongodb://root:1234@cckb9998.synology.me:27017/
|
||||||
|
db_name=KakaoChatData
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from configparser import ConfigParser
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 로깅 설정
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logger = logging.getLogger('default_logger')
|
||||||
|
|
||||||
|
class MongoConfig:
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(MongoConfig, cls).__new__(cls)
|
||||||
|
cls._instance.init_config()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def get_mongo_uri(self):
|
||||||
|
# MongoDB URI를 반환하는 메서드
|
||||||
|
return self.mongo_uri
|
||||||
|
|
||||||
|
def get_config_path(self):
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
application_path = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(application_path, 'config.ini')
|
||||||
|
|
||||||
|
def init_config(self):
|
||||||
|
config_path = self.get_config_path()
|
||||||
|
self.config = ConfigParser()
|
||||||
|
self.config.read(config_path)
|
||||||
|
logger.debug(f"Config loaded from {config_path}")
|
||||||
|
self.client = None
|
||||||
|
self.load_config()
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
if not self.config.has_section('MongoDB'):
|
||||||
|
logger.error('Config section for MongoDB is missing.')
|
||||||
|
raise Exception('Config section for MongoDB is missing.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.mongo_uri = self.config.get('MongoDB', 'uri')
|
||||||
|
self.db_name = self.config.get('MongoDB', 'db_name')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Missing MongoDB configuration option: {e}")
|
||||||
|
raise Exception(f"Missing configuration option: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def try_connect(self):
|
||||||
|
try:
|
||||||
|
self.client = MongoClient(self.mongo_uri)
|
||||||
|
self.db = self.client[self.db_name]
|
||||||
|
logger.debug("MongoDB connection established.")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to connect to MongoDB: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
class DatabaseManager:
|
||||||
|
def __init__(self, mongo_uri):
|
||||||
|
self.mongo_uri = mongo_uri
|
||||||
|
self.client = None
|
||||||
|
self.db = None
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
try:
|
||||||
|
self.client = MongoClient(self.mongo_uri)
|
||||||
|
self.db = self.client['KakaoChatData'] # 고정된 데이터베이스 이름
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect to MongoDB: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def insert_documents(self, collection_name, documents):
|
||||||
|
if self.db is not None: # 수정된 조건문
|
||||||
|
collection = self.db[collection_name]
|
||||||
|
collection.insert_many(documents)
|
||||||
|
print(f"Inserted documents into {collection_name}")
|
||||||
|
return True
|
||||||
|
print("Database not connected.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def insert_if_not_exists(self, collection_name, document):
|
||||||
|
query = {
|
||||||
|
'datetime': document['datetime'],
|
||||||
|
'user': document['user'],
|
||||||
|
'message': document['message'] # 대화 내용
|
||||||
|
}
|
||||||
|
update = {
|
||||||
|
'$setOnInsert': document
|
||||||
|
}
|
||||||
|
collection = self.db[collection_name] # 채팅방 이름을 컬렉션 이름으로 사용
|
||||||
|
result = collection.update_one(query, update, upsert=True)
|
||||||
|
if result.upserted_id is not None:
|
||||||
|
print(f"Inserted new document with ID: {result.upserted_id}")
|
||||||
|
else:
|
||||||
|
print("Document already exists, no insertion made.")
|
||||||
|
|
||||||
|
|
||||||
|
def query_documents(self, collection_name, query):
|
||||||
|
if self.db is not None: # 수정된 조건문
|
||||||
|
collection = self.db[collection_name]
|
||||||
|
results = list(collection.find(query))
|
||||||
|
return results
|
||||||
|
print("Database not connected.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_documents_by_date_range(self, collection_name, start_date, end_date):
|
||||||
|
query = {
|
||||||
|
'datetime': {
|
||||||
|
'$gte': start_date,
|
||||||
|
'$lte': end_date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list(self.db[collection_name].find(query))
|
||||||
|
|
||||||
|
def search_documents_by_text(self, collection_name, text):
|
||||||
|
query = {
|
||||||
|
'message': {'$regex': text, '$options': 'i'} # 대소문자 구분 없이 검색
|
||||||
|
}
|
||||||
|
return list(self.db[collection_name].find(query))
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import pytest
|
||||||
|
from resources.chat_parser import parse_chat_log
|
||||||
|
|
||||||
|
def test_parse_chat_log():
|
||||||
|
test_input = """[Test Room]
|
||||||
|
--------------- 2024년 5월 9일 목요일 ---------------
|
||||||
|
[User1] [09:00] 안녕하세요
|
||||||
|
[User2] [09:01] 반갑습니다"""
|
||||||
|
expected_output = [
|
||||||
|
{'date': '2024-05-09', 'user': 'User1', 'time': '09:00', 'message': '안녕하세요'},
|
||||||
|
{'date': '2024-05-09', 'user': 'User2', 'time': '09:01', 'message': '반갑습니다'}
|
||||||
|
]
|
||||||
|
assert parse_chat_log(test_input) == expected_output
|
||||||
|
|
||||||
|
def test_empty_input():
|
||||||
|
assert parse_chat_log("") == []
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from src.database import DatabaseManager
|
||||||
|
|
||||||
|
def test_insert_chat_data(mocker):
|
||||||
|
db_manager = DatabaseManager()
|
||||||
|
mocker.patch.object(db_manager, 'insert_chat_data')
|
||||||
|
db_manager.insert_chat_data("chatroom1", [{"message": "test"}])
|
||||||
|
db_manager.insert_chat_data.assert_called_once()
|
||||||
|
|
||||||
|
def test_get_messages_by_user(mocker):
|
||||||
|
db_manager = DatabaseManager()
|
||||||
|
mocker.patch.object(db_manager.db.chatrooms, 'find')
|
||||||
|
db_manager.get_messages_by_user("chatroom1", "User1")
|
||||||
|
db_manager.db.chatrooms.find.assert_called_with({"_id": "chatroom1", "messages.user": "User1"}, {"messages.$": 1})
|
||||||
Loading…
Reference in New Issue