jack 9 месяцев назад
Сommit
6eb17c2e72

+ 67 - 0
.gitignore

@@ -0,0 +1,67 @@
+.DS_Store
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+.idea/*
+xml_files/
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+other/split_clash_config/split_config
+ai_news/save_data
+
+manual/clash/clash_each_node
+manual/singbox/singbox_each_node


+ 6 - 0
__init__.py

@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from . import security
+from . import models
+from . import views
+from . import wizard

+ 23 - 0
__manifest__.py

@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+{
+    "name": "Resource Collection",
+    "version": "1.0",
+    "author": "Jack",
+    "category": "auto_bot/Collect",
+    'summary': 'Resource Collection',
+    'description': """Resource Collection""",
+    "depends": ["base"],
+    "data": [
+        "security/ir.model.access.csv",
+        "security/security.xml",
+        "views/view_collection_index.xml",
+        "views/views_news_info.xml",
+        "views/view_collection_img.xml",
+        "wizard/views_sync_news_data.xml",
+        "wizard/views_delete_data.xml"
+    ],
+    "installable": True,
+    "auto_install": True,
+    "application": True,
+    'license': 'LGPL-3',
+}

+ 109 - 0
codes_backup/flaticon

@@ -0,0 +1,109 @@
+import sys
+import os
+import time
+import random
+
+import httpx
+from playwright.sync_api import sync_playwright
+
+target_base_name = 'flaticon'
+target_base_url = 'https://www.flaticon.com'
+title_selector = '#pack-view__inner > section.pack-view__header > h1'
+selector = '#pack-view__inner > section.search-result > ul > li:nth-child({}) > div > a > img'
+img_selector = '#detail > div > div.row.detail__top.mg-none > section > div > div > div.row.row--vertical-center.mg-none.full-height.detail__icon__inner > div > div > img'
+img_count_selector = '#pack-view__inner > section.pack-view__header > p'
+not_find_page_selector = '#viewport > div.errorpage.e404 > h1'
+clean_string_seed = ['<', '>', ':', '"', '/', f'\\', '|', '?', '*', '.', '  ', 'Icon Pack ']
+
+all_data = {}
+
+#  ----------------------------------   open_browser   -------------------------------------------
+pages = '/{}'
+urls = []
+file_path = ''  # 存放图片的文件夹
+title = ''  # 存放当前页面的title
+with sync_playwright() as playwright:
+    if self.show_browser:
+        browser = playwright.webkit.launch(headless=False)
+    else:
+        browser = playwright.webkit.launch(headless=True)
+    context = browser.new_context(viewport={'width': 1280, 'height': 700})
+    page = context.new_page()
+
+    img_sequence_num = 1
+    for page_count in range(1, 999):
+        try:
+            goto_url = self.img_set_url + pages.format(page_count)
+            page.goto(goto_url, timeout=5000)
+        except Exception as e:
+            print(e)
+            print(f'Page load failed: url is : {goto_url}')
+
+        # 检查一下当前页面是不是 404
+        try:
+            page.wait_for_selector(not_find_page_selector, state="attached", timeout=2000)
+            print(f'Total page is {page_count - 1}')
+            break
+        except:
+            pass
+
+        if page_count == 1:
+            # 获取title
+            page.wait_for_selector(title_selector, state="attached", timeout=10000)
+            title = page.query_selector(title_selector).inner_text()
+
+            for char in clean_string_seed:
+                title = title.replace(char, '')
+            title = title.replace(' ', '_')
+
+
+            # 获取当前图片合集总 img 数量
+            img_count = page.query_selector(img_count_selector).inner_text()
+            img_count = int(img_count.split(' ')[0])
+
+        for i in range(1, img_count + 1):
+            # 选择所有的<a>标签
+            elements = page.query_selector_all(selector.format(i))
+
+            # 遍历所有<a>标签,提取href属性
+            for element in elements:
+                src = element.get_attribute('src')
+                if src:
+                    src = src.replace('/128/', '/512/')
+                    suffix = src.split('.')[-1]
+                    sequence = str(img_sequence_num).zfill(3)
+                    urls.append({
+                        'url': src,
+                        'img': f'{title}_{sequence}',
+                        'suffix': suffix
+                    })
+                    img_sequence_num += 1
+                    break
+
+    print(f'All image URLs have been obtained. Total img {len(urls)}')
+
+    page.close()
+    browser.close()
+
+# --------------------------------------------------------------   process data  --------------------------------------------------------------
+
+save_line = []
+if urls:
+    n = 1
+    for data in urls:
+        save_line += [[0, 0, {
+                    'serial': n,
+                    'url': data['url'],
+                    'name': data['img'],
+                    'image_suffix': data['suffix']
+                }]]
+        n += 1
+
+
+self.update({
+    'file_title': title,
+    'line_ids': save_line,
+    'image_count': len(urls)
+})
+
+print('done!')

+ 5 - 0
models/__init__.py

@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import collection_index
+from . import collection_img
+from . import news_info

+ 152 - 0
models/collection_img.py

@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+import time
+import os
+import zipfile
+
+import httpx
+
+from odoo import models, fields, api
+from odoo.exceptions import UserError
+
+
+class CollectionImg(models.Model):
+    _name = 'collection.img'
+    _description = 'Collection Img'
+    _order = 'create_date DESC'
+
+    line_ids = fields.One2many('collection.img.line', 'collection_img_id', string='Img Lines')
+
+    name = fields.Char(string='Name', default=str(int(time.time())))
+
+    target_site_id = fields.Many2one('collection.img.target.site', string='Target Base Site')
+
+    file_title = fields.Char(string='File Title')
+
+    img_set_url = fields.Char(string='Image Set URL')
+
+    show_browser = fields.Boolean(string='Show Browser', default=False)
+
+    image_count = fields.Integer(string='Image Count')
+
+    def btn_get_img_data(self):
+        # 删除之前保存的 line
+        self.btn_clear_data()
+
+        if self.target_site_id.name not in self.img_set_url:
+            raise UserError('target web site incorrect')
+
+        self._check_file_exist()
+
+        try:
+            exec(self.target_site_id.codes)
+        except Exception as e:
+            raise UserError(str(e))
+
+    def btn_download_img(self):
+        self._check_file_exist()
+
+        # 检查一下 line 有没有缺少 url
+        for line in self.line_ids:
+            if not line.url:
+                raise UserError(f'{line.name}{line.serial} Missing image address')
+
+        # 先查看下载位置的文件夹, 有没有这个图片合集的文件夹
+        download_path = os.path.join('/tmp/collection_downloads', self.target_site_id.name, self.file_title)
+        if not os.path.exists(download_path):
+            os.mkdir(download_path)
+
+        # 准备一个列表存放图片路径, 然后打包下载
+        img_path_list = []
+
+        # 一行一行处理数据, 以后或者成使用多线程或者对进程或协程
+        for line in self.line_ids:
+            if line.set_name:
+                # 这里是有分图片合集的情况, 即类似 comico
+                pass
+            else:
+                # 这里是没有分图片合集的情况, 即类似 图片资源站
+                img_file_path = os.path.join(download_path, line.name + '.' + line.image_suffix)
+                if os.path.exists(img_file_path):  # 这个图片存在就直接加入打包列表, 否则就下载
+                    img_path_list.append(img_file_path)
+                    line.update({'download_state': 'downloaded'})
+                    continue
+
+                # 这里开始下载图片
+                retry_count = 10
+                while retry_count:
+                    try:
+                        img = httpx.get(line.url, headers={
+                            "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
+                        })
+                        if img.status_code == 200:
+                            with open(img_file_path, 'wb') as f:
+                                f.write(img.content)
+                            line.update({'download_state': 'downloaded'})
+                            img_path_list.append(img_file_path)
+                            break
+                        else:
+                            time.sleep(5)
+                            retry_count -= 1
+                    except Exception as e:
+                        print(str(e))
+                        time.sleep(5)
+                        retry_count -= 1
+
+        # 打这里所有的 img 都已经下载完, 并且所有图片路径已经存入 img_path_list 中
+        # 这里开始打包下载好的图片
+        zip_file_path = os.path.join(download_path, self.file_title + '.zip')
+        with zipfile.ZipFile(zip_file_path, 'w') as zip_file:
+            for img_path in img_path_list:
+                zip_file.write(img_path, os.path.basename(img_path))
+
+        # 构建下载地址
+        download_url = f"{download_path}.zip"
+        print(download_url)
+        # 返回一个动作字典,用于下载文件
+        return {
+            'type': 'ir.actions.act_url',
+            "url": download_url
+        }
+
+    def btn_clear_data(self):
+        for line in self.line_ids:
+            line.unlink()
+
+    def _check_file_exist(self):
+        # 如果下载位置的文件夹不存在, 则创建一个先
+        download_path = '/tmp/collection_downloads'
+        if not os.path.exists(download_path):
+            os.mkdir(download_path)
+
+        # 然后查看对应网站的储存文件夹在不在,不在就创建一个
+        target_site_name = os.path.join(download_path, self.target_site_id.name)
+        if not os.path.exists(target_site_name):
+            os.mkdir(target_site_name)
+
+
+class CollectionImgLine(models.Model):
+    _name = 'collection.img.line'
+    _description = 'Collection Img Line'
+
+    collection_img_id = fields.Many2one('collection.img', string='Collection Img', ondelete='cascade')
+
+    name = fields.Char(string='Name')
+    set_name = fields.Char(string='Set Name')
+    serial = fields.Integer(string='Serial')
+    url = fields.Char(string='URL')
+
+    download_state = fields.Selection([('not_download', 'not download'), ('downloaded', 'downloaded')],
+                                      string='Download State', default='not_download')
+
+    image_suffix = fields.Char(string='Image Suffix')
+
+
+class CollectionImgTargetSite(models.Model):
+    _name = 'collection.img.target.site'
+    _description = 'Collection Img Target Site'
+
+    name = fields.Char(string='Name')
+
+    target_url = fields.Char(string='Target URL')
+
+    codes = fields.Text(string='Codes')

+ 8 - 0
models/collection_index.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from odoo import fields, models
+
+
+class CollectIndex(models.Model):
+    _name = 'collection.index'
+    _description = 'Collection Index'
+    _transient = True

+ 173 - 0
models/news_info.py

@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+from datetime import datetime, timedelta
+from pymongo import MongoClient
+import smtplib
+from email.mime.text import MIMEText
+from email.header import Header
+from odoo.exceptions import UserError
+
+
+class News(models.Model):
+    _name = 'news.info'
+    _description = 'News Info'
+    _order = 'create_datetime DESC'
+
+    name = fields.Char(string='Title', required=True)
+    context = fields.Text(string='Context')
+    context_simple = fields.Char(string='Context Simple', compute='_compute_context')
+    source_url = fields.Char(string='Source URL')
+    link = fields.Char(string='Link')
+    article_type = fields.Char(string='Article Type')
+    article_source = fields.Char(string='Article Source')
+    img_url = fields.Char(string='Image URL')
+    keyword = fields.Char(string='Keyword')
+    posted_date = fields.Char(string='Posted Date')
+    create_time_ts = fields.Float(string='Creation Time TS')
+    create_time = fields.Datetime(string='Creation Time')
+    create_datetime = fields.Datetime(string='Creation Datetime')
+
+    def _compute_context(self):
+        context = ''
+        for record in self:
+            if record.context:
+                if len(record.context) < 80:
+                    record.context_simple = record.context
+                else:
+                    record.context_simple = f'{record.context[:80]}...'
+            else:
+                record.context_simple = context
+
+
+class SearchKeys(models.Model):
+    _name = 'news.search_keys'
+    _description = 'News Search Keys'
+    _order = 'id DESC'
+
+    def _default_name(self):
+        search_keys_id = self.env['news.search_keys'].search([], limit=1, order='id DESC')
+        if search_keys_id:
+            return str(int(search_keys_id.name) + 1).zfill(5)
+        else:
+            return '1'.zfill(5)
+
+    name = fields.Char(string='Name', default=_default_name)
+
+    keys = fields.Char(string='Keys')
+
+    text = fields.Text(string='Text')
+
+    auto_send = fields.Boolean(string='Auto Send', default=True)
+
+    search_days = fields.Integer(string='Search Days', default=7, required=False)
+
+    def btn_search(self):
+        try:
+            client = MongoClient('mongodb://root:aaaAAA111!!!@erhe.top:38000/', connectTimeoutMS=30000,
+                                 serverSelectionTimeoutMS=5000)
+
+            # 指定数据库名称
+            db_name = 'NEWS'  # 替换为你的数据库名称
+
+            # 选择数据库
+            db = client[db_name]
+
+            # 列出数据库中的所有集合
+            collections = db.list_collection_names()
+        except Exception as e:
+            raise UserError(e)
+
+        all_data = []
+        search_days = 7  # 假设你想搜索最近7天的数据
+
+        # 计算日期范围
+        current_date = datetime.utcnow()
+        date_threshold = (current_date - timedelta(days=self.search_days)).strftime('%Y-%m-%d %H:%M:%S')
+
+        # 将 self.keys 字符串分割成列表,如果 self.keys 为空,则设置为空列表
+        keys_list = self.keys.split('|||') if self.keys else []
+
+        for collection_name in collections:
+            # 选择集合
+            collection = db[collection_name]
+
+            # 构建查询条件
+            query_conditions = {'create_time': {'$gte': date_threshold}}
+
+            # 如果有传入关键词,添加到查询条件中
+            if keys_list:
+                for search_key in keys_list:
+                    # 构建查询
+                    query = {
+                        '$or': [
+                            {'title': {'$regex': search_key, '$options': 'i'}},  # 搜索title字段
+                            {'context': {'$regex': search_key, '$options': 'i'}}  # 搜索context字段
+                        ],
+                        'create_datetime': {'$gte': date_threshold}  # create_datetime字段大于等于计算出的日期
+                    }
+
+                    # 执行查询
+                    results = collection.find(query, {'_id': 0})
+                    for result in results:
+                        all_data.append(result)
+            else:
+                query = {'create_datetime': {'$gte': date_threshold}}
+                results = collection.find(query, {'_id': 0})
+
+                for result in results:
+                    all_data.append(result)
+
+        sorted_data = []
+
+        if all_data:
+            sorted_data = sorted(all_data, key=lambda x: x['create_time'], reverse=True)
+        else:
+            return
+
+        if sorted_data:
+            if self.search_days:
+                t = '关键词: {}\n{} 天内数据\n{}\n\n'.format(self.keys, self.search_days, '*' * 100)
+            t = '{} 天内数据\n{}\n\n'.format(self.search_days, '*' * 100)
+            for d in sorted_data:
+                title = d.setdefault('title')
+                context = d.setdefault('context') or '/'
+                link = d.setdefault('link') or '/'
+                posted_date = d.setdefault('posted_date') or '/'
+                create_datetime = d.setdefault('create_datetime')
+                if len(context) > 300:
+                    context = context[:300] + '...'
+                t += f"标题: {title}\n内容: {context}\n链接: {link}\n发表时间: {posted_date}\n获取时间: {create_datetime}\n"
+                t += '*' * 100 + '\n\n'
+
+            self.write({'text': t})
+        else:
+            self.write({'text': 'No Data'})
+
+        if self.auto_send:
+            self.btn_send_email()
+
+    def btn_send_email(self):
+        if self.text:
+            mail_host = 'smtp.163.com'
+            mail_user = 'pushmessagebot@163.com'
+            mail_pass = 'WSMSRKBKXIHIQWTU'
+
+            sender = 'pushmessagebot@163.com'
+            receivers = ['pushmessagebot@163.com']
+
+            message = MIMEText(self.text, 'plain', 'utf-8')
+            message['From'] = Header(f'keyword: {self.keys}' if self.keys else f'{self.search_days}天内数据', 'utf-8')
+            message['To'] = Header("SearchAndSend", 'utf-8')
+            message['Subject'] = Header(f'keyword: {self.keys}' if self.keys else f'{self.search_days}天内数据',
+                                        'utf-8')
+
+            try:
+                smtp_obj = smtplib.SMTP_SSL(mail_host)
+                smtp_obj.login(mail_user, mail_pass)
+                smtp_obj.sendmail(sender, receivers, message.as_string())
+                print("邮件发送成功")
+            except smtplib.SMTPException as e:
+                print("Error: 无法发送邮件", e)
+        else:
+            raise UserError('请先搜索数据')

+ 11 - 0
security/ir.model.access.csv

@@ -0,0 +1,11 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+collect_index_access,Collect Index Access,model_collection_index,base.group_user,1,1,1,1
+
+access_news_news,view_news.view_news,model_news_info,base.group_user,1,1,1,1
+sync_news_data_access,Sync News Data Access,model_auto_news_sync,base.group_user,1,1,1,0
+delete_news_data_access,Delete News Data Access,model_auto_news_delete,base.group_user,1,1,1,1
+sync_search_keys_access,Keys Search Access,model_news_search_keys,base.group_user,1,1,1,1
+
+collection_img_access,Collection Img Access,model_collection_img,base.group_user,1,1,1,1
+collection_img_line_access,Collection Img Line Access,model_collection_img_line,base.group_user,1,1,1,1
+collection_target_access,Collection Img Target Site Access,model_collection_img_target_site,base.group_user,1,1,1,1

+ 76 - 0
security/security.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+    <record id="collection_index_access" model="ir.model.access">
+        <field name="name">Collection Index Access</field>
+        <field name="model_id" ref="model_collection_index"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="1"/>
+    </record>
+
+    <!-- Access Right for Sync News Data -->
+    <record id="sync_news_data_access" model="ir.model.access">
+        <field name="name">Sync News Data Access</field>
+        <field name="model_id" ref="model_auto_news_sync"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="0"/>
+    </record>
+
+    <!-- Access Right for Delete News Data -->
+    <record id="delete_news_data_access" model="ir.model.access">
+        <field name="name">Delete News Data Access</field>
+        <field name="model_id" ref="model_auto_news_delete"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="1"/>
+    </record>
+
+    <!-- Access Right for Search Keys -->
+    <record id="sync_search_keys_access" model="ir.model.access">
+        <field name="name">Keys Search Access</field>
+        <field name="model_id" ref="model_news_search_keys"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="0"/>
+    </record>
+
+    <record id="collection_img_access" model="ir.model.access">
+        <field name="name">Collection Img Access</field>
+        <field name="model_id" ref="model_collection_img"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="1"/>
+    </record>
+
+    <record id="collection_img_line_access" model="ir.model.access">
+        <field name="name">Collection Img Line Access</field>
+        <field name="model_id" ref="model_collection_img_line"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="1"/>
+    </record>
+
+    <record id="collection_target_access" model="ir.model.access">
+        <field name="name">Collection Img Target Site Access</field>
+        <field name="model_id" ref="model_collection_img_target_site"/>
+        <field name="group_id" ref="base.group_user"/>
+        <field name="perm_read" eval="1"/>
+        <field name="perm_write" eval="1"/>
+        <field name="perm_create" eval="1"/>
+        <field name="perm_unlink" eval="1"/>
+    </record>
+
+</odoo>

+ 143 - 0
views/view_collection_img.xml

@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+    <!-- CollectionImg Tree View -->
+    <record id="collection_img_tree" model="ir.ui.view">
+        <field name="name">collection.img.tree</field>
+        <field name="model">collection.img</field>
+        <field name="arch" type="xml">
+            <list string="Collection Img">
+                <field name="target_site_id"/>
+                <field name="file_title"/>
+                <field name="image_count"/>
+                <field name="create_date"/>
+            </list>
+        </field>
+    </record>
+
+    <record id="collection_img_form" model="ir.ui.view">
+        <field name="name">collection.img.form</field>
+        <field name="model">collection.img</field>
+        <field name="arch" type="xml">
+            <form string="Collection Img">
+                <header>
+                    <button name="btn_get_img_data" type="object" string="Get Img" class="btn-primary"/>
+                    <button name="btn_download_img" type="object" string="Downloads" class="btn-info"/>
+                    <button name="btn_clear_data" type="object" string="Crean Line" class="btn-danger"/>
+                </header>
+                <sheet>
+                    <group>
+                        <group>
+                            <field name="file_title"/>
+                            <field name="show_browser" widget="boolean_toggle"/>
+                            <field name="image_count" readonly="True" widget="integer"/>
+                        </group>
+                        <group>
+                            <field name="target_site_id"
+                                   options="{'no_open': True, 'no_create': True, 'no_create_edit': True}"/>
+                            <field name="img_set_url"/>
+                        </group>
+                    </group>
+                    <notebook>
+                        <page string="Img Lines">
+                            <field name="line_ids">
+                                <list string="Img Lines" create="false" edit="false" delete="false">
+                                    <field name="serial"/>
+                                    <field name="url"/>
+                                    <field name="set_name"/>
+                                    <field name="name"/>
+                                    <field name="image_suffix"/>
+                                    <field name="download_state"/>
+                                </list>
+                            </field>
+                        </page>
+                    </notebook>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="collection_img_line_form" model="ir.ui.view">
+        <field name="name">collection.img.line.form</field>
+        <field name="model">collection.img.line</field>
+        <field name="arch" type="xml">
+            <form string="Collection Img Line">
+                <sheet>
+                    <group>
+                        <group>
+
+                            <field name="serial" readonly="True"/>
+                            <field name="image_suffix" readonly="True"/>
+                            <field name="download_state" readonly="True"/>
+                        </group>
+                        <group>
+                            <field name="name" readonly="True"/>
+                            <field name="set_name" readonly="True"/>
+                        </group>
+                        <field name="url" readonly="True" nolabel="1" widget="image_url"/>
+                    </group>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="collection_img_search" model="ir.ui.view">
+        <field name="name">collection.img.search</field>
+        <field name="model">collection.img</field>
+        <field name="arch" type="xml">
+            <search string="Collection Img Search">
+                <group expand="0" string="Group By">
+                    <filter name="target_site_id" context="{'group_by': 'target_site_id'}"/>
+                </group>
+            </search>
+        </field>
+    </record>
+
+    <record id="action_collection_img" model="ir.actions.act_window">
+        <field name="name">Collection Img</field>
+        <field name="res_model">collection.img</field>
+        <field name="view_mode">list,form</field>
+        <field name="view_id" ref="collection_img_tree"/>
+        <field name="context">{'search_default_target_site_id': 1}</field>
+    </record>
+
+    <record id="collection_img_target_site_tree" model="ir.ui.view">
+        <field name="name">collection.img.target.site.tree</field>
+        <field name="model">collection.img.target.site</field>
+        <field name="arch" type="xml">
+            <list string="Collection Img Target Site">
+                <field name="name"/>
+                <field name="target_url"/>
+            </list>
+        </field>
+    </record>
+
+    <record id="collection_img_target_site_form" model="ir.ui.view">
+        <field name="name">collection.img.target.site.form</field>
+        <field name="model">collection.img.target.site</field>
+        <field name="arch" type="xml">
+            <form string="Collection Img Target Site">
+                <sheet>
+                    <group>
+                        <group>
+                            <field name="name"/>
+                        </group>
+                        <group>
+                            <field name="target_url"/>
+                        </group>
+                    </group>
+                    <field name="codes" nolabel="1" colspan="2" widget="ace" options="{'mode': 'python'}"/>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="action_collection_img_target_site" model="ir.actions.act_window">
+        <field name="name">Collection Img Target Sites</field>
+        <field name="res_model">collection.img.target.site</field>
+        <field name="view_mode">list,form</field>
+    </record>
+
+    <menuitem id="menu_collection_icon_index" name="Collection Icon" parent="collection_index_menu" action="action_collection_img" sequence="2"/>
+    <menuitem id="menu_collection_img" name="Collection Icon" parent="menu_collection_icon_index" action="action_collection_img" sequence="1"/>
+    <menuitem id="menu_collection_img_target_site" name="Collection Img Target Sites" action="action_collection_img_target_site" parent="menu_collection_icon_index" sequence="2"/>
+</odoo>

+ 20 - 0
views/view_collection_index.xml

@@ -0,0 +1,20 @@
+<odoo>
+    <record id="collection_index_tree_view" model="ir.ui.view">
+        <field name="name">collection.index.tree.view</field>
+        <field name="model">collection.index</field>
+        <field name="arch" type="xml">
+            <list string="Collection Index" create="0">
+            </list>
+        </field>
+    </record>
+
+    <record id="collection_index_action" model="ir.actions.act_window">
+        <field name="name">Collection Index</field>
+        <field name="type">ir.actions.act_window</field>
+        <field name="res_model">collection.index</field>
+        <field name="view_mode">list</field>
+        <field name="view_id" ref="collection_index_tree_view"/>
+    </record>
+
+    <menuitem id="collection_index_menu" name="Collection Index" action="collection_index_action" sequence="2"/>
+</odoo>

+ 154 - 0
views/views_news_info.xml

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+    <record id="view_news_tree" model="ir.ui.view">
+        <field name="name">news.info.tree</field>
+        <field name="model">news.info</field>
+        <field name="arch" type="xml">
+            <list string="News" limit="80" create="0">
+                <field name="name"/>
+                <field name="context_simple"/>
+                <field name="keyword"/>
+                <field name="article_source"/>
+                <field name="article_type"/>
+                <field name="posted_date"/>
+                <field name="create_time"/>
+            </list>
+        </field>
+    </record>
+
+    <record id="view_news_form" model="ir.ui.view">
+        <field name="name">news.info.form</field>
+        <field name="model">news.info</field>
+        <field name="arch" type="xml">
+            <form string="News" edit="0">
+                <sheet>
+                    <group>
+                        <field name="name"/>
+                        <group>
+                            <field name="keyword"/>
+                            <field name="article_type"/>
+                            <field name="link" widget="url"/>
+                            <field name="create_datetime"/>
+                            <field name="img_url"/>
+                        </group>
+                        <group>
+                            <field name="posted_date"/>
+                            <field name="article_source"/>
+                            <field name="create_time"/>
+                            <field name="source_url" widget="url"/>
+                        </group>
+                        <field name="context"/>
+                    </group>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="view_news_search" model="ir.ui.view">
+        <field name="name">news.info.search</field>
+        <field name="model">news.info</field>
+        <field name="arch" type="xml">
+            <search string="Search News">
+                <group>
+                    <filter string="Title" name="name_filter" domain="[('name', 'ilike', self)]"/>
+                    <filter string="Context" name="context_filter" domain="[('context', 'ilike', self)]"/>
+                    <filter string="Article Source" name="source_filter" domain="[('article_source', 'ilike', self)]"/>
+                    <filter string="Article Type" name="type_filter" domain="[('article_type', 'ilike', self)]"/>
+                    <filter string="Keyword" name="keyword_filter" domain="[('keyword', 'ilike', self)]"/>
+                </group>
+                <!-- 添加一个搜索输入框 -->
+                <field name="name"/>
+                <field name="context"/>
+                <field name="article_source"/>
+                <field name="article_type"/>
+                <field name="keyword"/>
+                <newline/>
+                <!-- 添加一个搜索按钮 -->
+                <group expand="1">
+                    <button name="search" string="Search" type="object" icon="fa-search" class="oe_stat_button" invisible="[('name', '=', False)], ('context', '=', False)"/>
+                </group>
+            </search>
+        </field>
+    </record>
+
+    <record id="action_news_info" model="ir.actions.act_window">
+        <field name="name">News Info</field>
+        <field name="res_model">news.info</field>
+        <field name="view_mode">list,form</field>
+    </record>
+
+    <record id="view_news_search_keys_tree" model="ir.ui.view">
+        <field name="name">news.search_keys.tree</field>
+        <field name="model">news.search_keys</field>
+        <field name="arch" type="xml">
+            <list string="News" limit="80">
+                <field name="name"/>
+                <field name="keys"/>
+                <field name="text"/>
+                <field name="create_date"/>
+            </list>
+        </field>
+    </record>
+
+    <record id="view_news_search_keys_form" model="ir.ui.view">
+        <field name="name">news.search_keys.form</field>
+        <field name="model">news.search_keys</field>
+        <field name="arch" type="xml">
+            <form string="News">
+                <header>
+                    <button name="btn_search" string="Search" type="object" class="oe_highlight"/>
+                    <button name="btn_send_email" string="Send Email" type="object" class="oe_highlight"/>
+                </header>
+                <sheet>
+                    <group>
+                        <group>
+                            <field name="name" readonly="1"/>
+                            <field name="keys"/>
+                        </group>
+                        <group>
+                            <field name="auto_send" widget="boolean_toggle"/>
+                            <field name="search_days"/>
+                        </group>
+                    </group>
+                    <group>
+                        <field name="text" widget="text" readonly="1"/>
+                    </group>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="view_news_search_keys_search" model="ir.ui.view">
+        <field name="name">news.news_search_keys.search</field>
+        <field name="model">news.search_keys</field>
+        <field name="arch" type="xml">
+            <search string="Search News">
+                <group>
+                    <filter string="Title" name="name_filter" domain="[('name', 'ilike', self)]"/>
+                    <filter string="Context" name="context_filter" domain="[('keys', 'ilike', self)]"/>
+                    <filter string="Article Source" name="source_filter" domain="[('text', 'ilike', self)]"/>
+                </group>
+
+                <field name="name"/>
+                <field name="keys"/>
+                <field name="text"/>
+                <newline/>
+
+                <group expand="1">
+                    <button name="search" string="Search" type="object" icon="fa-search" invisible="[('name', '=', False)], ('context', '=', False)"/>
+                </group>
+            </search>
+        </field>
+    </record>
+
+    <record id="action_news_search_keys_info" model="ir.actions.act_window">
+        <field name="name">News Keys Search</field>
+        <field name="res_model">news.search_keys</field>
+        <field name="view_mode">list,form</field>
+    </record>
+
+    <menuitem id="collection_index_menu" name="Collection Index" action="collection_index_action" sequence="2"/>
+    <menuitem id="news" name="News" action="action_news_info" parent="collection_index_menu" sequence="1"/>
+    <menuitem id="menu_news" name="News Info" action="action_news_info" parent="news" sequence="1"/>
+    <menuitem id="menu_keys_search" name="News Keys Search" action="action_news_search_keys_info" parent="news" sequence="2"/>
+</odoo>

+ 3 - 0
wizard/__init__.py

@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import wizard_sync_news_data
+from . import wizard_delete_data

+ 41 - 0
wizard/views_delete_data.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+    <record id="view_delete_news_data_wizard" model="ir.ui.view">
+        <field name="name">auto.news.delete.wizard</field>
+        <field name="model">auto.news.delete</field>
+        <field name="arch" type="xml">
+            <form string="Delete News Data" version="7.0">
+                <sheet>
+                    <group>
+                        <group>
+                            <field name="delete_all_data" widget="boolean_toggle"/>
+                        </group>
+                    </group>
+                    <group>
+                        <group>
+                            <field name="from_date" invisible="delete_all_data == True"/>
+                        </group>
+                        <group>
+                            <field name="to_date" invisible="delete_all_data == True"/>
+                        </group>
+                    </group>
+                    <footer>
+                        <button string='Confirm' name="btn_confirm" type="object" class="btn-primary" confirm="Whether to confirm the deletion of data"/>
+                        <button string="Cancel" class="btn-secondary" special="cancel"/>
+                    </footer>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="action_delete_news_data_wizard" model="ir.actions.act_window">
+        <field name="name">Delete News Data Wizard</field>
+        <field name="res_model">auto.news.delete</field>
+        <field name="view_mode">form</field>
+        <field name="view_id" ref="view_delete_news_data_wizard"/>
+        <field name="target">new</field>
+    </record>
+
+    <menuitem id="menu_delete_news_data" name="Delete News Data"
+              parent="news" action="action_delete_news_data_wizard"/>
+</odoo>

+ 30 - 0
wizard/views_sync_news_data.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+    <record id="view_sync_news_data_wizard" model="ir.ui.view">
+        <field name="name">auto.news.sync.wizard</field>
+        <field name="model">auto.news.sync</field>
+        <field name="arch" type="xml">
+            <form string="Synchronize News Data" version="7.0">
+                <sheet>
+                    <label for="message"/>
+                    <h1><field name="message"/></h1>
+                    <footer>
+                        <button string='Confirm' name="btn_sync_data" type="object" class="btn-primary"/>
+                        <button string="Cancel" class="btn-secondary" special="cancel"/>
+                    </footer>
+                </sheet>
+            </form>
+        </field>
+    </record>
+
+    <record id="action_sync_news_data_wizard" model="ir.actions.act_window">
+        <field name="name">Synchronize News Data Wizard</field>
+        <field name="res_model">auto.news.sync</field>
+        <field name="view_mode">form</field>
+        <field name="view_id" ref="view_sync_news_data_wizard"/>
+        <field name="target">new</field>
+    </record>
+
+    <menuitem id="menu_sync_news_data" name="Synchronize News Data"
+              parent="news" action="action_sync_news_data_wizard"/>
+</odoo>

+ 30 - 0
wizard/wizard_delete_data.py

@@ -0,0 +1,30 @@
+# -*- coding: UTF-8 -*-
+import time
+from datetime import date, datetime, timedelta
+from odoo import models, fields, _
+from odoo.exceptions import UserError
+from odoo.tools import date_utils
+
+
+class DeleteNewsData(models.TransientModel):
+    _name = 'auto.news.delete'
+    _description = 'Auto News Delete'
+
+    delete_all_data = fields.Boolean('Delete All Data')
+
+    from_date = fields.Date('Date From', default=date_utils.start_of(date.today(), 'month'))
+
+    to_date = fields.Date('To', default=date_utils.end_of(date.today(), 'month'))
+
+    def btn_confirm(self):
+        if self.delete_all_data:
+            self.env['news.info'].search([]).unlink()
+        else:
+            from_ts = time.mktime(self.from_date.timetuple())
+
+            to_ts = time.mktime(self.to_date.timetuple())
+
+            news_id = self.env['news.info'].search([('create_time_ts', ">=", str(from_ts)), ('create_time_ts', '<=', str(to_ts))])
+
+            if news_id:
+                news_id.unlink()

+ 70 - 0
wizard/wizard_sync_news_data.py

@@ -0,0 +1,70 @@
+# -*- coding: UTF-8 -*-
+from datetime import datetime
+
+from odoo import models, fields, _
+from pymongo import MongoClient
+
+from odoo.exceptions import UserError
+
+
+class SyncNewsData(models.TransientModel):
+    _name = 'auto.news.sync'
+    _description = 'Auto News Sync'
+
+    message = fields.Text('Message', default='确认同步数据?', readonly=True)
+
+    def btn_sync_data(self):
+        try:
+            client = MongoClient('mongodb://root:aaaAAA111!!!@erhe.top:38000/')
+
+            # 指定数据库名称
+            db_name = 'NEWS'  # 替换为你的数据库名称
+
+            # 选择数据库
+            db = client[db_name]
+
+            # 列出数据库中的所有集合
+            collections = db.list_collection_names()
+
+        except Exception as e:
+            raise UserError('连接数据库失败: {}'.format(e))
+
+        all_data = []
+
+        for collection_name in collections:
+            # 选择集合
+            collection = db[collection_name]
+
+            # 读取集合中的所有数据
+            for document in collection.find({}, {'_id': 0}):
+                all_data.append(document)
+
+        sorted_data = []
+
+        if all_data:
+            sorted_data = sorted(all_data, key=lambda x: x['create_time'], reverse=True)
+
+        for doc in sorted_data:
+            news_data_id = self.env['news.info'].search([('name', '=', doc['title'])], limit=1)
+
+            if news_data_id:
+                continue
+
+            create_time_dt = None
+            if doc.get('create_time'):
+                create_time_dt = datetime.utcfromtimestamp(doc['create_time'])
+
+            news_data = news_data_id.create({
+                'name': doc.get('title'),
+                'context': doc.get('context') or '',
+                'source_url': doc.get('source_url') or '',
+                'link': doc.get('line') or '',
+                'article_type': doc.get('article_type') or '',
+                'article_source': doc.get('article_source') or '',
+                'img_url': doc.get('img_url') or '',
+                'keyword': doc.get('keyword') or '',
+                'posted_date': doc.get('posted_date') or '',
+                'create_time_ts': doc.get('create_time') or '',
+                'create_time': create_time_dt,
+                'create_datetime': datetime.strptime(doc['create_datetime'], '%Y-%m-%d %H:%M:%S')
+            })