Кто ставил бесплатный софт. тот знает на сколько это больно )) так и с раундкубом, я так и не нашел рабочий плагин для индексирования писем для быстрого поиска по телу письма. вот такой костыль сделал.
Без плагинов, своими руками, с ограничением по пользователю и iframe сверху
# Установите Python 3 и pip (если ещё не установлены)
sudo apt update
sudo apt install python3 python3-pip python3-venv
# Установите необходимые Python-библиотеки
pip3 install mysql-connector-python chardet
# Установите MySQL/MariaDB (если ещё не установлен)
sudo apt install mariadb-server mariadb-client
# Установите клиентскую библиотеку Python для MySQL
sudo apt install python3-dev libmysqlclient-dev # Для Debian/Ubuntu
pip3 install mysqlclient # Альтернативный драйвер (может работать лучше)
python3 -m venv /opt/maildir_venv
/opt/maildir_venv/bin/pip install mysql-connector-python chardet
Проверка зависимостей
python3 -c "import mysql.connector; import chardet; print('OK')"
Создай таблицу для хранения индексированных писем:
CREATE TABLE IF NOT EXISTS email_index (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
path VARCHAR(512) NOT NULL,
subject TEXT,
from_email TEXT,
body LONGTEXT,
message_id VARCHAR(255),
file_name VARCHAR(255),
uid INT UNSIGNED DEFAULT NULL,
FULLTEXT (body)
);
Сохраните этот скрипт как /usr/local/bin/maildir_multiuser_indexer.py:
#!/opt/maildir_venv/bin/python
import os
import email
from email.parser import BytesParser
from email import policy
import mysql.connector
import chardet
MAILDIR_DOMAIN_PATH = "/var/mail/..."
DB_CONFIG = {
"host": "localhost",
"user": "roundcube",
"password": "roundcube12345",
"database": "roundcubemail"
}
def get_decoded_body(msg):
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
payload = part.get_payload(decode=True)
if payload:
encoding = chardet.detect(payload)['encoding'] or 'utf-8'
return payload.decode(encoding, errors='replace')
else:
payload = msg.get_payload(decode=True)
if payload:
encoding = chardet.detect(payload)['encoding'] or 'utf-8'
return payload.decode(encoding, errors='replace')
return ""
def connect_db():
return mysql.connector.connect(**DB_CONFIG)
def already_indexed(cursor, file_path):
cursor.execute("SELECT 1 FROM email_index WHERE path = %s", (file_path,))
return cursor.fetchone() is not None
def load_uid_map(user_maildir):
"""Читаем dovecot-uidlist из корневой папки пользователя"""
uidlist_path = os.path.join(user_maildir, "dovecot-uidlist")
if not os.path.exists(uidlist_path):
print(f"❌ Файл dovecot-uidlist не найден: {uidlist_path}")
return {}
uid_map = {}
with open(uidlist_path, 'r') as f:
lines = f.readlines()
for line in lines:
if line.startswith('1 '): # формат: 1 <uid> <filename>
parts = line.strip().split()
if len(parts) >= 3:
try:
uid = int(parts[1])
filename = parts[2]
uid_map[filename] = uid
except ValueError:
continue # игнорируем некорректные строки
return uid_map
def index_mail_file(cursor, file_path, user_id, uid_map):
try:
filename = os.path.basename(file_path)
uid = uid_map.get(filename)
with open(file_path, 'rb') as f:
raw_email = f.read()
msg = BytesParser(policy=policy.default).parsebytes(raw_email)
subject = msg['subject']
from_email = msg['from']
body = get_decoded_body(msg)
message_id = msg['message-id'] or ''
if not body:
print(f"⚠️ Не удалось извлечь текст из {file_path}")
return
cursor.execute("""
INSERT INTO email_index (user_id, path, subject, from_email, body, message_id, file_name, uid)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (user_id, file_path, subject, from_email, body, message_id, filename, uid))
print(f"✅ Проиндексировано: {file_path} | UID: {uid}")
except Exception as e:
print(f"❌ Ошибка при обработке {file_path}: {e}")
def get_user_id(cursor, email_address):
cursor.execute("SELECT user_id FROM users WHERE username = %s LIMIT 1", (email_address,))
result = cursor.fetchone()
return result[0] if result else None
def main():
db = connect_db()
cursor = db.cursor()
indexed_count = 0
if not os.path.exists(MAILDIR_DOMAIN_PATH):
print(f"❌ Папка домена не найдена: {MAILDIR_DOMAIN_PATH}")
return
print("\n📬 Обработка всех пользователей домена logo-academia.ru")
for user_email_dir in os.listdir(MAILDIR_DOMAIN_PATH):
user_email_path = os.path.join(MAILDIR_DOMAIN_PATH, user_email_dir)
if not os.path.isdir(user_email_path):
continue
print(f"\n📧 Обработка пользователя: {user_email_dir}")
user_id = get_user_id(cursor, user_email_dir)
if not user_id:
print(f"❌ Пользователь {user_email_dir} не найден в базе Roundcube")
continue
# Загружаем UID map из корневой папки пользователя
uid_map = load_uid_map(user_email_path)
for folder in ["cur", "new"]:
folder_path = os.path.join(user_email_path, folder)
if not os.path.exists(folder_path):
print(f"📁 Папка '{folder}' не найдена для {user_email_dir}")
continue
print(f"📂 Читаю папку: {folder_path}")
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
if not os.path.isfile(file_path):
continue
if already_indexed(cursor, file_path):
print(f"🟨 Уже проиндексировано: {file_path}")
continue
index_mail_file(cursor, file_path, user_id, uid_map)
indexed_count += 1
print(f"📨 Найдено новых писем для {user_email_dir}: {indexed_count}")
db.commit()
cursor.close()
db.close()
print(f"\n📬 Всего проиндексировано новых писем: {indexed_count}")
print("✅ Индексация завершена.")
if __name__ == "__main__":
main()
sudo -u www-data /opt/maildir_venv/bin/python /usr/local/bin/maildir_multiuser_indexer.py
Правим файл:
После строки:
$_SESSION['user_id'] = $user_id;
Добавляем:
file_put_contents("/tmp/roundcube_login_{$_SERVER['REMOTE_ADDR']}.txt", $this->get_user_name());
<?php
$ip = $_SERVER['REMOTE_ADDR'];
$login_file = "/tmp/roundcube_login_{$ip}.txt";
if (!file_exists($login_file)) {
die("❌ Вы не авторизованы");
}
$email = trim(file_get_contents($login_file));
try {
$pdo = new PDO("mysql:host=localhost;dbname=roundcubemail;charset=utf8mb4", "roundcube", "roundcube123");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (\PDOException $e) {
die('Ошибка подключения к БД: ' . $e->getMessage());
}
$search = $_GET['q'] ?? '';
$results = [];
if ($search && strlen($search) >= 3) {
$stmt = $pdo->prepare("
SELECT
ei.id,
u.username AS email,
ei.subject,
ei.from_email,
ei.body,
MATCH(ei.body) AGAINST(:search IN NATURAL LANGUAGE MODE) AS relevance
FROM email_index ei
JOIN users u ON ei.user_id = u.user_id
WHERE MATCH(ei.body) AGAINST(:search IN NATURAL LANGUAGE MODE)
AND u.username = :email
ORDER BY relevance DESC
LIMIT 50
");
$stmt->execute([':search' => $search, ':email' => $email]);
$results = $stmt->fetchAll();
}
Редактируем тему Roundcube:
/var/www/html/webmail/skins/elastic/templates/mail.html
Вставляем перед
Добавьте в
Теперь у вас:
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
<iframe src="/webmail/search_email_best.php"
style="width:100%; height:150px; border:none;"
scrolling="yes">
</iframe>🔄 Шаг 6: (Опционально) Автоматизация индексации
crontab -e:0 2 * * * sudo -u www-data /opt/maildir_venv/bin/python /usr/local/bin/maildir_multiuser_indexer.py🎉 Готово!
Оставить комментарий
Обратная связь
Важно: Блог-эксперимент
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA
Комментарии
Пока нет комментариев. Будьте первым!