diff --git a/Anime/README.md b/Anime/README.md new file mode 100644 index 0000000..f0abc44 --- /dev/null +++ b/Anime/README.md @@ -0,0 +1,3 @@ +# Anime Channel + +This directory is dedicated to discussions and content about anime. \ No newline at end of file diff --git a/Games/README.md b/Games/README.md new file mode 100644 index 0000000..6143c99 --- /dev/null +++ b/Games/README.md @@ -0,0 +1,3 @@ +# Games Channel + +This directory is dedicated to discussions and content about games. \ No newline at end of file diff --git a/Politics/README.md b/Politics/README.md new file mode 100644 index 0000000..2ea54bd --- /dev/null +++ b/Politics/README.md @@ -0,0 +1,3 @@ +# Politics Channel + +This directory is dedicated to discussions and content about politics. \ No newline at end of file diff --git a/README.md b/README.md index 038ae64..1585101 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,53 @@ -# FuckingChat +# FuckingChat ๐ +Encrypted chat with channel support. Messages are encrypted at rest using Fernet (PBKDF2 + SHA256). + +## Channels + +- ๐ฌ **General** โ general discussion +- ๐ฎ **Games** โ gaming chat +- ๐ **Anime** โ anime & manga +- โ๏ธ **Politics** โ politics discussion + +## Setup + +```bash +# Install dependencies +pip install bottle cryptography sqlalchemy + +# Run (development) +python index.py + +# Run (production - recommended) +CHAT_SECRET="your-strong-secret" \ +CHAT_SALT="your-random-salt" \ +CHAT_ADMIN_TOKEN="your-admin-token" \ +CHAT_RATE_LIMIT=5 \ +CHAT_HOST=0.0.0.0 \ +CHAT_PORT=8080 \ +python index.py +``` + +## Environment Variables + +| Variable | Default | Description | +|---|---|---| +| `CHAT_SECRET` | `default_secret_change_me` | Encryption key | +| `CHAT_SALT` | `default_salt_change_me` | PBKDF2 salt | +| `CHAT_ADMIN_TOKEN` | (none) | Token for DELETE API | +| `CHAT_RATE_LIMIT` | `5` | Max messages per window | +| `CHAT_RATE_WINDOW` | `10` | Rate limit window (seconds) | +| `CHAT_MAX_MSG` | `2000` | Max message length | +| `CHAT_DB_PATH` | `./chat.db` | SQLite database path | +| `CHAT_HOST` | `localhost` | Listen address | +| `CHAT_PORT` | `8080` | Listen port | +| `CHAT_DEBUG` | `0` | Debug mode (set to `1` for dev) | + +## Security Features + +- โ Messages encrypted at rest (AES via Fernet) +- โ Rate limiting (configurable) +- โ IP hashing (privacy) +- โ XSS protection (HTML tag stripping) +- โ Admin token required for deletion +- โ No debug mode in production diff --git a/chat.db b/chat.db deleted file mode 100644 index 0d5345d..0000000 Binary files a/chat.db and /dev/null differ diff --git a/dbworker.py b/dbworker.py deleted file mode 100644 index 9e77111..0000000 --- a/dbworker.py +++ /dev/null @@ -1,191 +0,0 @@ -import sqlalchemy -from sqlalchemy import create_engine, String, Integer, Text, DateTime -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session -from sqlalchemy.orm import sessionmaker -from datetime import datetime -import time -import os -from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -import base64 - -# Encryption setup -SALT = b'salt_123456789' -PASSWORD = b'chat_secret_key_123' - -def get_cipher(): - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=SALT, - iterations=100000, - ) - key = base64.urlsafe_b64encode(kdf.derive(PASSWORD)) - return Fernet(key) - -class Base(DeclarativeBase): - pass - -class Messages(Base): - __tablename__ = "messages" - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - ip: Mapped[str] = mapped_column(String(64), nullable=False) - content: Mapped[str] = mapped_column(Text, nullable=False) - timestamp: Mapped[int] = mapped_column(Integer, nullable=False) - -# MariaDB configuration -DB_USER = 'chat' -DB_PASSWORD = 'uqhyUb5eBGg3qad.' -DB_HOST = 'localhost' -DB_PORT = '3306' -DB_NAME = 'chat_db' - -# Create engine for MariaDB -DATABASE_URL = f"mariadb+mariadbconnector://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" -# Alternative with pymysql (uncomment if needed): -# DATABASE_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" - -engine = create_engine( - DATABASE_URL, - echo=False, - pool_size=10, - max_overflow=20, - pool_pre_ping=True, - pool_recycle=3600 -) - -# Create tables -Base.metadata.create_all(engine) - -class DBfrontend(): - def __init__(self): - self.Session = sessionmaker(bind=engine) - self.cipher = get_cipher() - - def encrypt_message(self, message: str) -> str: - """Encrypt message before storing""" - return self.cipher.encrypt(message.encode()).decode() - - def decrypt_message(self, encrypted_message: str) -> str: - """Decrypt message after retrieval""" - try: - return self.cipher.decrypt(encrypted_message.encode()).decode() - except: - return "[Decryption failed]" - - def add_message(self, ip: str, content: str) -> int: - """Add a new message to the database and return its ID""" - with self.Session() as session: - encrypted_content = self.encrypt_message(content) - new_message = Messages( - ip=ip, - content=encrypted_content, - timestamp=int(time.time() * 1000) - ) - session.add(new_message) - session.commit() - return new_message.id - - def get_all_messages(self) -> list: - """Get all messages from the database""" - with self.Session() as session: - messages = session.query(Messages).all() - for msg in messages: - msg.content = self.decrypt_message(msg.content) - return messages - - def get_message_by_id(self, message_id: int) -> Messages | None: - """Get a specific message by its ID""" - with self.Session() as session: - message = session.query(Messages).get(message_id) - if message: - message.content = self.decrypt_message(message.content) - return message - - def get_messages_by_ip(self, ip: str) -> list: - """Get all messages from a specific IP address""" - with self.Session() as session: - messages = session.query(Messages).filter(Messages.ip == ip).all() - for msg in messages: - msg.content = self.decrypt_message(msg.content) - return messages - - def update_message(self, message_id: int, new_content: str) -> bool: - """Update the content of a message""" - with self.Session() as session: - message = session.query(Messages).get(message_id) - if message: - message.content = self.encrypt_message(new_content) - session.commit() - return True - return False - - def delete_message(self, message_id: int) -> bool: - """Delete a message by its ID""" - with self.Session() as session: - message = session.query(Messages).get(message_id) - if message: - session.delete(message) - session.commit() - return True - return False - - def delete_messages_by_ip(self, ip: str) -> int: - """Delete all messages from a specific IP address and return count""" - with self.Session() as session: - messages = session.query(Messages).filter(Messages.ip == ip).all() - count = len(messages) - for message in messages: - session.delete(message) - session.commit() - return count - - def get_message_count(self) -> int: - """Get total number of messages""" - with self.Session() as session: - return session.query(Messages).count() - - def get_latest_messages(self, limit: int = 10) -> list: - """Get the latest N messages""" - with self.Session() as session: - messages = session.query(Messages).order_by(Messages.id.desc()).limit(limit).all() - for msg in messages: - msg.content = self.decrypt_message(msg.content) - return messages - - def clear_all_messages(self) -> int: - """Delete all messages and return count""" - with self.Session() as session: - count = session.query(Messages).count() - session.query(Messages).delete() - session.commit() - return count - - def get_messages_by_date_range(self, start_timestamp: int, end_timestamp: int) -> list: - """Get messages within a timestamp range""" - with self.Session() as session: - messages = session.query(Messages).filter( - Messages.timestamp >= start_timestamp, - Messages.timestamp <= end_timestamp - ).order_by(Messages.timestamp.desc()).all() - for msg in messages: - msg.content = self.decrypt_message(msg.content) - return messages - -# Optional: Test connection function -def test_connection(): - """Test if MariaDB connection is working""" - try: - with engine.connect() as conn: - result = conn.execute(sqlalchemy.text("SELECT 1")) - print("โ MariaDB connection successful!") - return True - except Exception as e: - print(f"โ MariaDB connection failed: {e}") - return False - -# Run test if executed directly -if __name__ == "__main__": - test_connection() \ No newline at end of file diff --git a/index.html b/index.html index 54b90af..1143a96 100644 --- a/index.html +++ b/index.html @@ -3,459 +3,297 @@
-