├── docs ├── logo.png └── wechat.png ├── requirements.txt ├── .streamlit ├── config.toml └── pages.toml ├── .gitignore ├── pages ├── exit.py ├── my.py ├── accounts.py ├── auto.py ├── tasks.py └── hots.py ├── utils ├── auth.py ├── local_storage.py ├── translation.py ├── ai_tools.py ├── sql_data.py ├── text_to_image.py ├── server.py ├── client.py ├── sqlit_manage.py ├── get_hot_data.py └── auto_tools.py ├── config.py ├── manage ├── account.py ├── task.py ├── user.py └── common.py ├── update.bat ├── webui.bat ├── main.py ├── README.md ├── README.en.md └── LICENSE /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/AIMedia/main/docs/logo.png -------------------------------------------------------------------------------- /docs/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/AIMedia/main/docs/wechat.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/AIMedia/main/requirements.txt -------------------------------------------------------------------------------- /.streamlit/config.toml: -------------------------------------------------------------------------------- 1 | [theme] 2 | base="dark" 3 | #primaryColor="purple" 4 | 5 | [general] 6 | debug=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | local_config.py 3 | .venv 4 | *.pyc 5 | chrome 6 | *.db 7 | temp 8 | setup.py 9 | rename.py 10 | venv.tar.gz 11 | chrome.rar -------------------------------------------------------------------------------- /pages/exit.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import streamlit as st 4 | from streamlit_local_storage import LocalStorage 5 | 6 | st.write("注销中...") 7 | LocalStorage().deleteAll() 8 | time.sleep(1) 9 | st.switch_page("main.py") 10 | -------------------------------------------------------------------------------- /utils/auth.py: -------------------------------------------------------------------------------- 1 | from manage.common import check_member 2 | from utils.local_storage import get_data 3 | 4 | 5 | # 检查会员是否过期 6 | def auth_level_expiry_time(): 7 | return check_member() 8 | 9 | 10 | def is_login(): 11 | token = get_data() 12 | return token 13 | -------------------------------------------------------------------------------- /utils/local_storage.py: -------------------------------------------------------------------------------- 1 | from streamlit_local_storage import LocalStorage 2 | 3 | 4 | # 保存数据到 localStorage 5 | def save_data(key, value): 6 | LocalStorage().setItem(key, value) 7 | 8 | 9 | # 从 localStorage 读取数据 10 | def get_data(): 11 | token = LocalStorage().getItem("token") 12 | return token 13 | 14 | 15 | def get_sessionid(): 16 | sessionid_token = LocalStorage().getItem('sessionid_token') 17 | return sessionid_token -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # 检查是否存在 local_config.py 文件 4 | if os.path.exists("local_config.py"): 5 | from local_config import * 6 | else: 7 | # 智普秘钥 8 | zhipu_aip_key = '' 9 | 10 | # stable diffusion 配置 11 | enable = False 12 | 13 | # 阿里翻译配置 14 | access_key = "" 15 | secret = "" 16 | language = "zh" 17 | 18 | # stable diffusion 配置 19 | sd_url = "http://127.0.0.1:7860/sdapi/v1/txt2img" 20 | # 图片宽 21 | firstphase_width = "1080" 22 | # 图片高 23 | firstphase_height = "960" 24 | # 反向提示词 25 | negative_prompt = "" 26 | -------------------------------------------------------------------------------- /.streamlit/pages.toml: -------------------------------------------------------------------------------- 1 | [[pages]] 2 | path = "main.py" 3 | name = "首页" 4 | icon = "🏠" 5 | 6 | [[pages]] 7 | path = "pages/hots.py" 8 | name = "热点库" 9 | icon = "🔥" 10 | url_path = "hots" 11 | 12 | [[pages]] 13 | path = "pages/accounts.py" 14 | name = "账号管理" 15 | icon = "👥" 16 | url_path = "accounts" 17 | 18 | [[pages]] 19 | path = "pages/tasks.py" 20 | name = "任务中心" 21 | icon = "🧾" 22 | url_path = "tasks" 23 | 24 | [[pages]] 25 | path = "pages/my.py" 26 | name = "个人中心" 27 | icon = "👨‍💼" 28 | url_path = "my" 29 | 30 | 31 | [[pages]] 32 | path = "pages/auto.py" 33 | name = "AI托管" 34 | icon = "🤖" 35 | url_path = "auto" 36 | 37 | 38 | [[pages]] 39 | path = "pages/exit.py" 40 | name = "退出登录" 41 | icon = "❌" 42 | url_path = "exit" 43 | -------------------------------------------------------------------------------- /manage/account.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/20 12:03 6 | # @file:account.py 7 | from manage.common import BaseRequest 8 | 9 | 10 | def get_account_list(params=None): 11 | base_request = BaseRequest() 12 | return base_request.get("user/account/", params=params) 13 | 14 | 15 | def create_account(data): 16 | base_request = BaseRequest() 17 | return base_request.post("user/account/", json=data) 18 | 19 | 20 | def delete_account(account_id): 21 | base_request = BaseRequest() 22 | return base_request.delete(f"user/account/{account_id}/") 23 | 24 | 25 | def use_activation_code(code): 26 | base_request = BaseRequest() 27 | return base_request.post(f"user/use_code/", json={"code": code}) 28 | -------------------------------------------------------------------------------- /update.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM 使用 PowerShell 设置终端输出为 UTF-8 编码 4 | powershell -Command "chcp 65001" 5 | 6 | REM 删除项目里的所有 .pyc 文件 7 | echo 删除项目里的所有 .pyc 文件 8 | for /r %%f in (*.pyc) do ( 9 | if exist "%%f" ( 10 | del "%%f" 11 | ) 12 | ) 13 | 14 | 15 | REM 删除 C 盘里的一个文件夹 16 | set folder_to_delete=C:\selenium 17 | echo 删除文件夹: %folder_to_delete% 18 | if exist "%folder_to_delete%" ( 19 | rmdir /s /q "%folder_to_delete%" 20 | echo 文件夹已删除 21 | ) else ( 22 | echo 文件夹不存在 23 | ) 24 | 25 | REM 执行 git pull 26 | echo 执行 git pull 27 | git pull 28 | 29 | REM 进入虚拟环境 30 | set venv_path=.\venv\Scripts\activate 31 | echo 进入虚拟环境: %venv_path% 32 | call %venv_path% 33 | 34 | 35 | REM 安装 requirements.txt 文件中的依赖项 36 | echo 安装 requirements.txt 文件中的依赖项 37 | pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --exists-action=s -r requirements.txt 38 | 39 | REM 运行 streamlit run main.py 40 | echo 运行 streamlit run main.py 41 | streamlit run main.py 42 | 43 | pause -------------------------------------------------------------------------------- /webui.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell -Command "chcp 65001" 3 | 4 | net session >nul 2>&1 5 | if %errorLevel% neq 0 ( 6 | echo 当前没有管理员权限。重新启动并请求管理员权限... 7 | powershell -Command "Start-Process '%~f0' -Verb RunAs" 8 | exit /b 9 | ) 10 | 11 | echo 已以管理员权限运行。 12 | set folder_to_delete=C:\selenium 13 | echo 删除文件夹: %folder_to_delete% 14 | if exist "%folder_to_delete%" ( 15 | rmdir /s /q "%folder_to_delete%" 16 | echo 文件夹已删除 17 | ) else ( 18 | echo 文件夹不存在 19 | ) 20 | 21 | echo 进入脚本所在磁盘 22 | set script_drive=%~d0 23 | cd /d "%script_drive%" 24 | echo 当前路径: %cd% 25 | 26 | echo 进入脚本所在目录 27 | cd /d "%~dp0" 28 | echo 当前路径: %cd% 29 | 30 | set venv_path=%cd%\venv\Scripts\activate.bat 31 | echo 虚拟环境路径: %venv_path% 32 | echo 进入虚拟环境: %venv_path% 33 | if exist "%venv_path%" ( 34 | call %venv_path% 35 | echo 虚拟环境已激活 36 | ) else ( 37 | echo 虚拟环境路径不存在,尝试第二种方法... 38 | goto second_method 39 | ) 40 | 41 | echo 运行 streamlit run main.py 42 | echo 当前路径: %cd% 43 | python -m streamlit run main.py 44 | 45 | pause 46 | exit /b 47 | 48 | :second_method 49 | echo 第二种方法: 进入虚拟环境 50 | set venv_path=.\venv\Scripts\activate 51 | echo 进入虚拟环境: %venv_path% 52 | call %venv_path% 53 | 54 | echo 运行 streamlit run main.py 55 | echo 当前路径: %cd% 56 | python -m streamlit run main.py 57 | 58 | pause -------------------------------------------------------------------------------- /manage/task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/20 12:54 6 | # @file:task.py 7 | from manage.common import BaseRequest 8 | 9 | 10 | def get_task_list(params=None): 11 | base_request = BaseRequest() 12 | return base_request.get("task/tasks/", params=params) 13 | 14 | 15 | def create_task_(data): 16 | base_request = BaseRequest() 17 | return base_request.post("task/tasks/", json=data) 18 | 19 | 20 | def update_task(task_id, data): 21 | base_request = BaseRequest() 22 | return base_request.put(f"task/tasks/{task_id}/", json=data) 23 | 24 | 25 | def partial_update_task(task_id, data): 26 | base_request = BaseRequest() 27 | return base_request.patch(f"task/tasks/{task_id}/", json=data) 28 | 29 | 30 | def delete_task(task_id): 31 | base_request = BaseRequest() 32 | return base_request.delete(f"task/tasks/{task_id}/") 33 | 34 | 35 | def get_task_info_list(params=None): 36 | base_request = BaseRequest() 37 | return base_request.get("task/task_info/", params=params) 38 | 39 | 40 | def create_task_info_(data): 41 | base_request = BaseRequest() 42 | return base_request.post("task/task_info/", json=data) 43 | 44 | 45 | def check_pub_time(uid): 46 | base_request = BaseRequest() 47 | return base_request.post(f"task/tasks/check_pub_time/", json={"uid": uid}) 48 | -------------------------------------------------------------------------------- /manage/user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/19 16:57 6 | # @file:user.py 7 | from manage.common import BaseRequest 8 | from utils.local_storage import save_data 9 | 10 | 11 | def register(phone, password, password2): 12 | if len(phone) != 11: 13 | return False, "请输入正确手机号码" 14 | else: 15 | if password != password2: 16 | return False, "两次输入的密码不一致" 17 | else: 18 | base_request = BaseRequest() 19 | response = base_request.post( 20 | "user/register/", 21 | json={"phone": phone, "password": password, "password2": password2}, 22 | ) 23 | access = response.get("access") 24 | if access: 25 | save_data("token", access) 26 | return True, "注册成功" 27 | else: 28 | return False, response 29 | 30 | 31 | def login(phone, password): 32 | base_request = BaseRequest() 33 | response = base_request.post( 34 | "user/login/", json={"phone": phone, "password": password} 35 | ) 36 | access = response.get("access") 37 | if access: 38 | save_data("token", access) 39 | return True, "登录成功" 40 | else: 41 | return False, response 42 | 43 | 44 | def get_user(): 45 | base_request = BaseRequest() 46 | response = base_request.get("user/user/") 47 | return response[0] 48 | 49 | 50 | if __name__ == "__main__": 51 | # reg = register("12345678901", "123456", "123456") 52 | log = login("12345678901", "123456") 53 | -------------------------------------------------------------------------------- /pages/my.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import requests 3 | 4 | from manage.account import use_activation_code 5 | from manage.user import get_user 6 | from utils.auth import is_login 7 | 8 | # 判断是否登录 9 | token = is_login() 10 | if not token: 11 | st.switch_page("main.py") 12 | else: 13 | st.session_state.token = token 14 | 15 | # 如果用户 phone 是 123,添加 "会员配置" 菜单 16 | user = get_user() 17 | 18 | user_data = { 19 | "username": user["phone"], 20 | "membership_level": user["level"], 21 | "expiration_date": user["expiry_time"], 22 | } 23 | 24 | 25 | # 模拟兑换卡密函数 26 | def activate_membership(card_key): 27 | # 这里可以替换为实际的兑换卡密接口请求代码 28 | response = use_activation_code(card_key) 29 | return response["result"] 30 | 31 | 32 | # 用户信息展示 33 | cols = st.columns(3) 34 | cols[0].write(f"**账号:** {user_data['username']}") 35 | cols[1].write(f"**会员等级:** Lv{user_data['membership_level']}") 36 | cols[2].write(f"**会员到期日期:** {user_data['expiration_date']}") 37 | 38 | # 会员激活 39 | st.header("会员激活") 40 | card_key = st.text_input("卡密") 41 | 42 | if st.button("兑换"): 43 | if card_key: 44 | result = activate_membership(card_key) 45 | if result: 46 | st.success("卡密兑换成功!") 47 | # 更新用户数据 48 | user_data["membership_level"] = result.get( 49 | "new_membership_level", user_data["membership_level"] 50 | ) 51 | user_data["expiration_date"] = result.get( 52 | "new_expiration_date", user_data["expiration_date"] 53 | ) 54 | st.write(f"**新的会员等级:** {user_data['membership_level']}") 55 | st.write(f"**新的会员到期日期:** {user_data['expiration_date']}") 56 | else: 57 | st.error("卡密兑换失败,请检查卡密是否正确。") 58 | else: 59 | st.warning("请输入卡密。") 60 | -------------------------------------------------------------------------------- /utils/translation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/13 16:29 6 | # @file:Translation.py 7 | import requests 8 | from alibabacloud_alimt20181012.client import Client as alimt20181012Client 9 | from alibabacloud_tea_openapi import models as open_api_models 10 | from alibabacloud_alimt20181012 import models as alimt_20181012_models 11 | from config import access_key, secret, language 12 | 13 | 14 | class Sample: 15 | def __init__(self): 16 | pass 17 | 18 | @staticmethod 19 | def create_client( 20 | access_key_id: str, 21 | access_key_secret: str, 22 | region_id: str, 23 | ) -> alimt20181012Client: 24 | config = open_api_models.Config() 25 | config.access_key_id = access_key_id 26 | config.access_key_secret = access_key_secret 27 | config.region_id = region_id 28 | config.connect_timeout = 5000 29 | config.read_timeout = 10000 30 | return alimt20181012Client(config) 31 | 32 | @staticmethod 33 | async def create_client_async( 34 | access_key_id: str, 35 | access_key_secret: str, 36 | region_id: str, 37 | ) -> alimt20181012Client: 38 | config = open_api_models.Config() 39 | config.access_key_id = access_key_id 40 | config.access_key_secret = access_key_secret 41 | config.region_id = region_id 42 | return alimt20181012Client(config) 43 | 44 | @staticmethod 45 | def main( 46 | text, 47 | region="cn-hangzhou", 48 | format_type="text", 49 | source_language=language, 50 | target_language="en", 51 | ) -> None: 52 | if language == "en": 53 | return text 54 | client = Sample.create_client(access_key, secret, region) 55 | request = alimt_20181012_models.TranslateGeneralRequest( 56 | format_type=format_type, 57 | source_language=source_language, 58 | target_language=target_language, 59 | source_text=text, 60 | ) 61 | response = client.translate_general(request) 62 | return response.body.data.translated 63 | 64 | @staticmethod 65 | async def main_async( 66 | text, 67 | region="cn-hangzhou", 68 | format_type="text", 69 | source_language="zh", 70 | target_language="en", 71 | ) -> None: 72 | client = await Sample.create_client_async(access_key, secret, region) 73 | request = alimt_20181012_models.TranslateGeneralRequest( 74 | format_type=format_type, 75 | source_language=source_language, 76 | target_language=target_language, 77 | source_text=text, 78 | ) 79 | response = await client.translate_general_async(request) 80 | return response.body.data.translated 81 | 82 | 83 | if __name__ == "__main__": 84 | print(Sample.main("你好")) 85 | -------------------------------------------------------------------------------- /utils/ai_tools.py: -------------------------------------------------------------------------------- 1 | import requests, json 2 | import re 3 | import random 4 | from zhipuai import ZhipuAI 5 | from config import zhipu_aip_key 6 | 7 | 8 | def hot2article(hot, cls): 9 | json_data = { 10 | 'appId': 'YfxxmUAkYiuwC5rFFzfcDzGfqlFwLpdp', 11 | 'sessionId': f'{random.randint(10 ** 18, 10 ** 19 - 1)}', 12 | 'versionCode': 1, 13 | 'versionType': 'audit', 14 | 'content': { 15 | 'query': { 16 | 'type': 'text', 17 | 'value': { 18 | "showText": f"热点话题:{hot},分类:{cls}", 19 | }, 20 | }, 21 | }, 22 | } 23 | 24 | response = requests.post( 25 | 'https://agent-proxy-ws.baidu.com/agent/call/conversation', 26 | json=json_data, 27 | ) 28 | text = "" 29 | for line in response.iter_lines(): 30 | if line: 31 | decoded_line = line.decode("utf-8").rstrip() 32 | if decoded_line.startswith("data:"): 33 | data = json.loads(decoded_line[5:]) # 去除"data:"前缀 34 | try: 35 | text += data["data"]["message"]["content"][0]["data"]["text"] 36 | except: 37 | pass 38 | return text.replace("*", "").replace("#", "") 39 | 40 | 41 | # 去水印 42 | def del_watermark(url_old): 43 | json_data = { 44 | 'appId': 'dKb2XbRQXaRM4A6svDFb0PUG0tJXoKkU', 45 | 'sessionId': f'{random.randint(10 ** 18, 10 ** 19 - 1)}', 46 | 'versionCode': 1, 47 | 'versionType': 'online', 48 | 'content': { 49 | 'query': { 50 | 'type': 'text', 51 | 'value': { 52 | 'showText': url_old, 53 | }, 54 | }, 55 | }, 56 | } 57 | response = requests.post( 58 | 'https://agent-proxy-ws.baidu.com/agent/call/conversation', 59 | json=json_data, 60 | ) 61 | url_ = '' 62 | for line in response.iter_lines(): 63 | if line: 64 | decoded_line = line.decode("utf-8").rstrip() 65 | if decoded_line.startswith("data:"): 66 | data = json.loads(decoded_line[5:]) # 去除"data:"前缀 67 | try: 68 | url = data['data']['message']['content'][0]['data']['image_url'] 69 | if len(url) > 10: 70 | url_ += url 71 | except: 72 | pass 73 | 74 | return url_ 75 | 76 | 77 | # 文章分类 78 | def title_clas(title): 79 | client = ZhipuAI(api_key=zhipu_aip_key) # 填写您自己的APIKey 80 | cls = '美食,旅行,站内玩法,话题互动,娱乐,社会,二次元,交通,亲子,体育,军事,剧情,动物萌宠,天气,才艺,文化教育,时尚,时政,校园,汽车,游戏,科技,财经' 81 | response = client.chat.completions.create( 82 | model="glm-4-flash", 83 | messages=[ 84 | {"role": "user", 85 | "content": f"你好,我给你一段内容,你理解分析后,将他在{cls}中分类,然后将分类的最终结果输出给我,只需要输出分类,不能有其他多余的回答,否则会搜到惩罚!,以下是内容:{title}"}, 86 | ], 87 | ) 88 | try: 89 | res = response.choices[0].message.content 90 | except: 91 | res = '未知' 92 | return res 93 | 94 | 95 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import streamlit as st 4 | from st_pages import add_page_title, get_nav_from_toml 5 | from utils.auth import is_login 6 | from manage.user import register as register_user, login as login_user 7 | 8 | 9 | @st.dialog("登录中...") 10 | def vote(phone, password): 11 | status, message = login_user(phone, password) 12 | if status: 13 | st.success(message) 14 | del st.session_state["page"] 15 | time.sleep(2) 16 | st.switch_page("pages/hots.py") 17 | else: 18 | if isinstance(message, dict) and "non_field_errors" in message: 19 | st.error("".join(message["non_field_errors"])) # 展示具体的错误信息 20 | else: 21 | st.error(message or "登录失败,请检查账号密码是否正确。") 22 | 23 | 24 | # 登录页面 25 | def login_page(): 26 | phone = st.text_input("账号", placeholder="请输入手机号码") 27 | password = st.text_input("密码", type="password", placeholder="请输入密码") 28 | if st.button("没有账号?点击注册", key="register_button"): 29 | st.session_state.page = "register" 30 | st.rerun() # 切换页面后重新渲染 31 | if st.button("登录"): 32 | vote(phone, password) 33 | 34 | 35 | # 注册页面 36 | def register_page(): 37 | phone = st.text_input("账号", placeholder="请输入手机号码") 38 | password = st.text_input("密码", type="password", placeholder="请输入密码") 39 | confirm_password = st.text_input("确认密码", type="password", placeholder="请再次输入密码") 40 | if st.button("已有账号?点击登录", key="login_button"): 41 | st.session_state.page = "login" 42 | st.rerun() # 切换页面后重新渲染 43 | if st.button("注册"): 44 | if len(phone) != 11: 45 | st.error("请输入正确手机号码") 46 | else: 47 | if password == confirm_password: 48 | status, message = register_user(phone, password, confirm_password) 49 | if status: 50 | st.success("登录成功!") 51 | del st.session_state["page"] 52 | time.sleep(2) 53 | st.switch_page("pages/hots.py") 54 | else: 55 | st.error(message) 56 | else: 57 | st.error("两次输入的密码不一致。") 58 | 59 | 60 | if __name__ == "__main__": 61 | st.set_page_config( 62 | page_title="Ai爆文大师", 63 | page_icon="🚀", 64 | layout="wide", 65 | initial_sidebar_state="auto", 66 | menu_items={"About": "接受定制"}, 67 | ) 68 | 69 | nav = get_nav_from_toml(".streamlit/pages.toml") 70 | 71 | st.logo("docs/logo.png") 72 | 73 | pg = st.navigation(nav) 74 | 75 | add_page_title(pg) 76 | 77 | pg.run() 78 | 79 | # 检查是否已经登录 80 | token = is_login() 81 | # 初始化页面状态 82 | if not token: 83 | if "page" not in st.session_state: 84 | st.session_state.page = "login" # 默认首次加载进入登录页面 85 | elif "page" not in st.session_state and st.session_state.page == "register": 86 | st.session_state.page = "register" # 默认首次加载进入登录页面 87 | else: 88 | st.session_state.token = token # 默认首次加载进入登录页面 89 | # 根据页面状态显示对应的页面 90 | if "page" in st.session_state and st.session_state.page == "login": 91 | login_page() 92 | elif "page" in st.session_state and st.session_state.page == "register": 93 | register_page() 94 | -------------------------------------------------------------------------------- /manage/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/19 16:58 6 | # @file:common.py 7 | import requests 8 | 9 | import streamlit as st 10 | 11 | 12 | class BaseRequest: 13 | def __init__(self): 14 | """ 15 | 初始化BaseRequest类 16 | """ 17 | self.base_url = "http://127.0.0.1:8000" 18 | self.headers = {"Content-Type": "application/json"} 19 | if "token" in st.session_state: 20 | token = st.session_state.token 21 | self.headers["Authorization"] = f"Bearer {token}" 22 | 23 | def get(self, endpoint, params=None): 24 | """ 25 | 发送GET请求 26 | :param endpoint: API接口的具体路径 27 | :param params: GET请求的查询参数 28 | :return: 返回请求的响应 29 | """ 30 | url = f"{self.base_url}/{endpoint}" # 确保URL拼接正确 31 | response = requests.get(url, headers=self.headers, params=params) 32 | return self._handle_response(response) 33 | 34 | def post(self, endpoint, data=None, json=None): 35 | """ 36 | 发送POST请求 37 | :param endpoint: API接口的具体路径 38 | :param data: POST请求的表单数据 39 | :param json: POST请求的JSON数据 40 | :return: 返回请求的响应 41 | """ 42 | url = f"{self.base_url}/{endpoint}" 43 | response = requests.post(url, headers=self.headers, data=data, json=json) 44 | return self._handle_response(response) 45 | 46 | def put(self, endpoint, data=None, json=None): 47 | """ 48 | 发送PUT请求 49 | :param endpoint: API接口的具体路径 50 | :param data: PUT请求的表单数据 51 | :param json: PUT请求的JSON数据 52 | :return: 返回请求的响应 53 | """ 54 | url = f"{self.base_url}/{endpoint}" 55 | response = requests.put(url, headers=self.headers, data=data, json=json) 56 | return self._handle_response(response) 57 | 58 | def patch(self, endpoint, data=None, json=None): 59 | """ 60 | 发送PATCH请求 61 | :param endpoint: API接口的具体路径 62 | :param data: PATCH请求的表单数据 63 | :param json: PATCH请求的JSON数据 64 | :return: 返回请求的响应 65 | """ 66 | url = f"{self.base_url}/{endpoint}" 67 | response = requests.patch(url, headers=self.headers, data=data, json=json) 68 | return self._handle_response(response) 69 | 70 | def delete(self, endpoint, params=None): 71 | """ 72 | 发送DELETE请求 73 | :param endpoint: API接口的具体路径 74 | :param params: DELETE请求的查询参数 75 | :return: 返回请求的响应 76 | """ 77 | url = f"{self.base_url}/{endpoint}" 78 | response = requests.delete(url, headers=self.headers, params=params) 79 | return self._handle_response(response) 80 | 81 | def _handle_response(self, response): 82 | """ 83 | 处理响应数据,判断是否请求成功 84 | :param response: requests库返回的响应对象 85 | :return: 返回解析后的JSON数据或响应状态 86 | """ 87 | if response.status_code == 500: 88 | raise Exception("服务器内部错误") 89 | else: 90 | try: 91 | return response.json() 92 | except ValueError: 93 | return response.text 94 | 95 | 96 | # 检查会员是否过期 97 | def check_member(): 98 | req = BaseRequest() 99 | response = req.get("user/check_member/") 100 | return response["result"], response["message"] 101 | -------------------------------------------------------------------------------- /pages/accounts.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from utils.auth import auth_level_expiry_time, is_login 3 | import time 4 | from utils.auto_tools import AutoTools 5 | from utils.sql_data import ( 6 | login_account, 7 | get_login_account, 8 | del_login_account, 9 | get_account_info, 10 | ) 11 | 12 | 13 | # 模拟登录函数 14 | def login(platform): 15 | # 这里可以替换为实际的登录接口请求代码 16 | auto = AutoTools() 17 | nickName, cookies, uid = auto.get_cookies(platform) 18 | login_account(nickName, cookies, uid, platform) 19 | 20 | 21 | @st.dialog("正在执行") 22 | def vote(): 23 | st.write(f"等待谷歌浏览器启动后人工登录") 24 | st.write(f"登录后不要人工干预") 25 | st.write(f"浏览器启动后可以继续增加账号,无需等待") 26 | 27 | 28 | @st.dialog("正在查看账号数据...") 29 | def data_vote(uid, platform): 30 | st.write(f"浏览器启动中,请耐心等代...") 31 | auto = AutoTools() 32 | cookie = get_account_info(uid) 33 | auto.get_acconut_data(cookie, platform) 34 | 35 | 36 | # 模拟请求数据函数 37 | def get_account_data(): 38 | res_list = get_login_account() 39 | # 这里可以替换为实际的接口请求代码 40 | return res_list 41 | 42 | 43 | # 判断是否登录 44 | token = is_login() 45 | if not token: 46 | st.switch_page("main.py") 47 | else: 48 | st.session_state.token = token 49 | 50 | # 初始化 session state 51 | if "show_form" not in st.session_state: 52 | st.session_state.show_form = False 53 | 54 | # 新增账号按钮 55 | cols = st.columns([0.8, 0.1]) 56 | with cols[1]: 57 | if st.button("新增账号"): 58 | if not auth_level_expiry_time(): 59 | st.warning("会员过期") 60 | time.sleep(3) 61 | st.rerun() 62 | st.session_state.show_form = True 63 | 64 | # 如果 show_form 为 True,则渲染表单 65 | if st.session_state.show_form: 66 | with cols[1]: 67 | form_placeholder = st.empty() 68 | with form_placeholder.form(key="add_account_form"): 69 | platforms = ["公众号", "小红书", "头条号"] 70 | selected_platform = st.selectbox( 71 | "选择平台", platforms, key="platform_selectbox" 72 | ) 73 | if st.form_submit_button("确定"): 74 | vote() 75 | login(selected_platform) 76 | st.session_state.show_form = False # 重置表单状态 77 | form_placeholder.empty() # 清除表单 78 | st.rerun() 79 | 80 | # 展示账号数据 81 | st.header("账号数据") 82 | platform_filter = st.selectbox("选择平台", ["全部", "公众号", "小红书", "头条号"]) 83 | account_data = get_account_data() 84 | filtered_data = [ 85 | account 86 | for account in account_data 87 | if platform_filter == "全部" or account["platform"] == platform_filter 88 | ] 89 | 90 | if filtered_data: 91 | cols = st.columns(7) 92 | cols[0].write("昵称") 93 | cols[1].write("UID") 94 | cols[2].write("分类") 95 | cols[3].write("是否过期") 96 | cols[4].write("归属平台") 97 | cols[5].write("操作") 98 | cols[6].write("数据分析") 99 | # cols[6].write("操作") 100 | for account in filtered_data: 101 | cols = st.columns(7) 102 | cols[0].write(account["nickname"]) 103 | cols[1].write(account["uid"]) 104 | cols[2].write(account["classify"]) 105 | cols[3].write("是" if account["expired"] else "否") 106 | cols[4].write(account["platform"]) 107 | if cols[5].button("删除账号", key=f"delete_{account['uid']}"): 108 | # 这里可以添加实际的删除账号逻辑 109 | del_login_account(account["id"]) 110 | st.rerun() 111 | if cols[6].button("账号数据", key=f"get_{account['uid']}"): 112 | # 这里可以添加实际的删除账号逻辑 113 | data_vote(account["uid"], account["platform"]) 114 | else: 115 | st.warning("没有账号数据。") 116 | -------------------------------------------------------------------------------- /utils/sql_data.py: -------------------------------------------------------------------------------- 1 | from manage.account import get_account_list, create_account, delete_account 2 | from manage.task import ( 3 | get_task_list, 4 | create_task_, 5 | delete_task, 6 | partial_update_task, 7 | create_task_info_, 8 | get_task_info_list, 9 | check_pub_time, 10 | ) 11 | from datetime import datetime, timedelta 12 | import json 13 | 14 | 15 | # 登录账号,如果存在跟新,不存在新增 16 | def login_account(nickName, cookies, uid, platform): 17 | # 获取今天的日期 18 | today = datetime.today() 19 | # 计算15天后的日期 20 | future_date = today + timedelta(days=15) 21 | # 将日期格式化为字符串 22 | expiry_time = future_date.strftime("%Y-%m-%d %H:%M:%S") 23 | 24 | create_account( 25 | data={ 26 | "nickname": nickName, 27 | "uid": uid, 28 | "cookie": cookies, 29 | "platform": platform, 30 | "expiry_time": expiry_time, 31 | "classify": "全部", 32 | } 33 | ) 34 | 35 | 36 | # 获取当前用户已经登录的账号 37 | def get_login_account(): 38 | accounts = get_account_list() 39 | 40 | res_list = [] 41 | for account in accounts: 42 | expiration_date = datetime.strptime(account["expiry_time"], "%Y-%m-%d %H:%M:%S") 43 | # 获取当前日期 44 | current_date = datetime.now() 45 | # 检查会员是否过期 46 | if current_date < expiration_date: 47 | expired = False 48 | else: 49 | expired = True 50 | dict_ = { 51 | "nickname": account["nickname"], 52 | "uid": account["uid"], 53 | "classify": account["classify"], 54 | "platform": account["platform"], 55 | "expired": expired, 56 | } 57 | res_list.append(dict_) 58 | return res_list 59 | 60 | 61 | # 获取指定账号信息 62 | def get_account_info(uid): 63 | result = get_account_list(params={"uid": uid}) 64 | return result[0]["cookie"] 65 | 66 | 67 | # 删除指定账号 68 | def del_login_account(uid): 69 | delete_account(uid) 70 | 71 | 72 | # 配置任务 73 | def create_task(classify, content, selected_account, img_list): 74 | # 获取当前用户信息 75 | nickname = selected_account["nickname"] 76 | uid = selected_account["uid"] 77 | platform = selected_account["platform"] 78 | 79 | task_old = get_task_list(params={"platform": platform, "task_opt": content}) 80 | if len(task_old) == 0: 81 | create_task_( 82 | { 83 | "account": selected_account["id"], 84 | "nickname": nickname, 85 | "uid": uid, 86 | "platform": platform, 87 | "classify": classify, 88 | "status": "已配置", 89 | "task_opt": content, 90 | "img_list": json.dumps(img_list), 91 | } 92 | ) 93 | return True 94 | else: 95 | return False 96 | 97 | 98 | # 获取账号当天任务 99 | def get_account_task(status=None): 100 | today_date = datetime.now().strftime("%Y-%m-%d") 101 | 102 | tasks = get_task_list( 103 | params={"created_time__date": today_date, "status_not": status} 104 | ) 105 | 106 | return tasks 107 | 108 | 109 | # 取消任务,删除指定任务 110 | def del_account_task(task_id): 111 | delete_task(task_id) 112 | 113 | 114 | # 跟新任务状态 115 | def update_account_task(task_id, status): 116 | partial_update_task(task_id, {"status": status}) 117 | 118 | 119 | # 记录任务详情 120 | def create_task_info(nickname, uid, platform, classify, content, task_id): 121 | # 获取当前用户信息 122 | create_task_info_( 123 | { 124 | "task": task_id, 125 | "nickname": nickname, 126 | "uid": uid, 127 | "platform": platform, 128 | "classify": classify, 129 | "content": content, 130 | } 131 | ) 132 | 133 | 134 | def get_task_info(task_id): 135 | result = get_task_info_list(params={"task": task_id}) 136 | return result[0]["uid"], result[0]["content"], result[0]["platform"] 137 | 138 | 139 | def get_last_time(uid): 140 | result = check_pub_time(uid) 141 | pub_time = result["pub_time"] 142 | if pub_time is None: 143 | return None 144 | datetime_obj = datetime.strptime(pub_time, "%Y-%m-%d %H:%M:%S") 145 | return datetime_obj 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

AIMedia 🤖

3 | 4 |

5 | Stargazers 6 | Issues 7 | Forks 8 | License 9 |

10 |
11 |

简体中文 | English

12 |
13 | 14 |
15 |
16 | 自动抓取热点,自动生成新闻,自动发布各大平台。 全自动托管AI媒体软件 17 |
18 |
19 |

由于项目已经开始商业化,plus版本不开源 点击进入官网

20 | 21 | 22 | ## 功能特性 🎯 23 | 24 | - [x] 支持 **热点新闻抓取**,自动抓取各大平台的热点新闻 25 | - [x] 抖音热点 26 | - [x] 网易新闻 27 | - [x] 微博热点 28 | - [x] 澎湃新闻 29 | - [x] 中国日报 30 | - [x] 搜狐新闻 31 | - [x] 支持 **根据新闻AI自动创作**,自动发布各个平台 32 | - [x] 今日头条 33 | - [x] 企鹅号 34 | - [x] 公众号 35 | - [x] 百家平台 36 | - [x] 针对无图纯文本,使用AI生成图像,增加原创率,阅读体验 37 | 38 | ### 后期计划 📅 39 | 40 | - [ ] 自动生成视频发布各个平台 41 | 42 | ## 交流讨论 💬 43 | 44 | 45 | 46 | ## 视频演示 📺 47 | 48 | B站视频链接:https://www.bilibili.com/video/BV1HABgYKE6H 49 | 50 | ## 配置要求 📦 51 | 52 | - 建议最低 CPU 4核或以上,内存 8G 或以上,显卡非必须 53 | - Windows 10 或以上 54 | 55 | ## 快速开始 🚀 56 | 57 | 下载一键启动包,解压直接使用(路径不要有 **中文**、**特殊字符**、**空格**) 58 | 59 | ### Windows 60 | - 百度网盘: https://pan.baidu.com/s/1YIV2avc_i5V8IcltWoFh1g 提取码:99k1 61 | 62 | 63 | 下载后,首先解压 venv.tar.gz 到当前目录venv下,结构如下 64 | 65 | ``` 66 | AIMedia 67 | ├─venv 68 | ├─main.py 69 | ├─chrome 70 | ├─... 71 | ``` 72 | 73 | 建议先**双击执行** update.bat 更新到**最新代码**(需要安装git),然后右键点击 **以管理员权限运行** webui.bat 启动 74 | 75 | 启动后,会自动打开浏览器(如果打开是空白,建议换成 **Chrome** 或者 **Edge** 打开) 76 | 77 | ### 其他系统 78 | 79 | 不支持,仅支持window 80 | 81 | ## 安装部署 📥 82 | 83 | ### 前提条件 84 | 85 | - 尽量不要使用 **中文路径**,避免出现一些无法预料的问题 86 | - 请确保你的 **网络** 是正常的,VPN需要打开全局流量模式 87 | 88 | #### ① 克隆代码 89 | 90 | ```shell 91 | git clone https://github.com/Anning01/AIMedia.git 92 | ``` 93 | 94 | #### ② 修改配置文件 95 | 96 | - 将 config.py 文件复制一份,命名为 local_config.py 97 | - 按照 config.py 文件中的说明,配置好 zhipu_aip_key,如需要AI配图,打开enable 配置相关的 stable diffusion api 98 | 99 | 100 | ### 手动部署 📦 101 | 102 | > 视频教程 103 | 104 | - 完整的使用演示:B站视频链接:hhttps://www.bilibili.com/video/BV1HABgYKE6H 105 | - 如何在Windows上部署:抓紧制作中 (*>﹏<*)′~ 106 | 107 | #### ① 创建虚拟环境 (Conda) 108 | 109 | 建议使用 [conda](https://www.anaconda.com/download/success) 创建 python 虚拟环境 110 | 111 | ```shell 112 | git clone https://github.com/Anning01/AIMedia.git 113 | cd AIMedia 114 | conda create -n AIMedia python=3.12.4 115 | conda activate AIMedia 116 | pip install -r requirements.txt 117 | ``` 118 | 119 | #### ② 启动Web界面 🌐 120 | 121 | 注意需要到 AIMedia 项目 根目录 下执行以下命令 122 | 123 | ###### Windows 124 | 125 | ```bat 126 | conda activate AIMedia 127 | streamlit run main.py 128 | ``` 129 | 130 | #### ① 使用venv (请确定 python 版本 3.12.4) 131 | 132 | ```shell 133 | git clone https://github.com/Anning01/AIMedia.git 134 | cd AIMedia 135 | python -m venv venv 136 | .\venv\Scripts\activate 137 | pip install -r requirements.txt 138 | ``` 139 | 140 | #### ② 启动Web界面 🌐 141 | 142 | 注意需要到 AIMedia 项目 根目录 下执行以下命令 143 | 144 | ###### Windows 145 | 146 | ```bat 147 | streamlit run main.py 148 | 或者 149 | .\webui.bat(conda不可以这样执行) 150 | ``` 151 | 152 | > 注意:我们自动发布依赖chrome测试版,需要手动下载 153 | 154 | 下载地址: 155 | 156 | - 百度网盘: 链接:https://pan.baidu.com/s/1x6J3K4KdWrI9vOG8yvSSBw 提取码:7jyw 157 | 158 | 159 | 模型下载后解压,整个目录放到 .\AIMedia 里面, 160 | 最终的文件路径应该是这样: .\AIMedia\chrome 161 | 162 | ## 反馈建议 📢 163 | 164 | - 可以提交 [issue](https://github.com/Anning01/AIMedia/issues) 165 | 或者 [pull request](https://github.com/Anning01/AIMedia/pulls)。 166 | 167 | 168 | ## 许可证 📝 169 | 170 | 点击查看 [LICENSE](LICENSE) 文件 171 | 172 | ## Star History 173 | 174 | [![Star History Chart](https://api.star-history.com/svg?repos=Anning01/AIMedia&type=Date)](https://star-history.com/#Anning01/AIMedia&Date) -------------------------------------------------------------------------------- /utils/text_to_image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | # @author:anning 4 | # @email:anningforchina@gmail.com 5 | # @time:2024/10/13 16:29 6 | # @file:text_to_image.py 7 | import base64 8 | import io 9 | import re 10 | 11 | import requests 12 | from PIL import Image 13 | 14 | from utils.translation import Sample 15 | from config import firstphase_width, firstphase_height, sd_url, negative_prompt 16 | 17 | 18 | def print_tip(tip, blank_line=0): 19 | """打印提示文字和空行""" 20 | blank = "\n" * blank_line if blank_line else "" 21 | 22 | 23 | class Main: 24 | def handle(self, text, quantity, save_path): 25 | """ 26 | 通过文本生成图片 27 | :param text: 生成图片的文本 28 | :param quantity: 需要配图的数量 29 | :return: 30 | """ 31 | # 首先将接收到的文本进行分段 32 | text_list = self.split_text_by_quantity(text, quantity) 33 | translates_list = [] 34 | 35 | 36 | # 将分段内容进行翻译 37 | for index_i, i in enumerate(text_list): 38 | translates_list.append(Sample.main(i)) 39 | 40 | # base64_image_list = [] 41 | # 将翻译内容传到Stable Diffusion生成图片 42 | for index_j, j in enumerate(translates_list): 43 | # base64_image_list.append(get_images(j)) 44 | image_bytes = base64.b64decode(get_images(j)) 45 | image = Image.open(io.BytesIO(image_bytes)) 46 | image.save(rf"{save_path}/{index_j}.jpg") 47 | 48 | # 最后返回图片 49 | # return base64_image_list 50 | 51 | def split_text_by_quantity(self, text, quantity): 52 | # 使用正则表达式按中文标点符号分句 53 | sentences = re.split(r"(。|!|?|;|,|、)", text) 54 | sentences = [s for s in sentences if s] # 移除空句子 55 | 56 | # 计算每个段落大致的句子数¬ 57 | sentences_per_part = len(sentences) // quantity 58 | remainder = len(sentences) % quantity 59 | 60 | result = [] 61 | part = [] 62 | count = 0 63 | 64 | for i, sentence in enumerate(sentences): 65 | part.append(sentence) 66 | count += 1 67 | # 如果达到分配的句子数,或者这是剩余句子之一,就进行分段 68 | if count >= sentences_per_part + (1 if remainder > 0 else 0): 69 | result.append("".join(part)) 70 | part = [] 71 | count = 0 72 | remainder -= 1 # 减少剩余句子的数量 73 | 74 | # 如果最后有剩余的部分也加入到结果中 75 | if part: 76 | result.append("".join(part)) 77 | 78 | return result 79 | 80 | 81 | base_data = { 82 | "seed": -1, 83 | "sampler_name": "Euler", 84 | "batch_size": 1, 85 | "steps": 8, 86 | "cfg_scale": 2, 87 | "restore_faces": True, 88 | } 89 | 90 | 91 | def get_images(text): 92 | prompt = text 93 | novel_dict = { 94 | "width": firstphase_width, 95 | "height": firstphase_height, 96 | "negative_prompt": negative_prompt, 97 | "prompt": prompt + "", 98 | **base_data, 99 | } 100 | try: 101 | response = requests.post(sd_url, json=novel_dict, timeout=30) 102 | img_response = response.json() 103 | except Exception as e: 104 | if str(e) == "Expecting value: line 2 column 1 (char 1)": 105 | raise Exception(f"{sd_url} 返回数据异常,请查看是否开启,或者是否连接成功。") 106 | raise Exception(response.text) 107 | images = img_response.get("images", None) 108 | if not images: 109 | error = img_response.get( 110 | "error", 111 | "Stable Diffusion 返回数据异常,请查看ip+端口是否匹配,是否开启。", 112 | ) 113 | raise Exception(error) 114 | return images[0] 115 | 116 | 117 | if __name__ == "__main__": 118 | text = "**牛市突然叫停!揭秘背后的十个重要原因,你不可不知!** 嘿,亲爱的财经小伙伴们,最近股市的风云突变,是不是让你们也感到措手不及呢?那个一路狂飙的牛市,怎么就突然来了个急刹车?别急,今天我就来给大家揭秘这背后的十个重要原因,保证让你们看得明明白白,还能引发一波热烈的评论互动哦! **一、监管爸爸出手:股市太热,得降温!** 哎呀,这股市啊,就像是个孩子,一旦玩起来就忘了形。监管部门一看,这不行啊,再这么疯下去,大家都要跳火坑了!于是,监管爸爸一拍桌子,决定给股市降降温。这一下子,牛市就像被浇了一盆冷水,瞬间熄火了。 **二、市场涨得太猛,人心慌慌,得刹车!** 你知道吗,当市场涨得太猛的时候,人心就开始慌了。大家都在想,这股市是不是泡沫太大了?监管部门一看,这不行啊,再这么涨下去,小车变火车,失控了怎么办?于是,他们果断出手,给牛市踩了个急刹车。 **三、经济基本面撑不住,泡沫太大,得叫停!** 咱们得说实话,这股市的狂热,经济基本面可是撑不住的。泡沫越吹越大,再不叫停的话,后果可是很严重的。所以啊,监管部门也是出于保护大家的考虑,才决定给牛市来个紧急叫停的。 **四、资金都跑股市去了,实体经济缺钱,得平衡!** 你知道吗,当大量资金都涌入股市的时候,实体经济可就缺钱了。这可不行啊,经济结构得平衡发展才行。于是呢,监管部门就出手了,给牛市踩了个刹车,让资金能够回流到实体经济中去。 **五、外部环境不稳定,牛市持续风险大,得谨慎!** 哎,这外部环境也是不稳定啊,不确定性增加了很多。监管部门一看,这牛市要是再持续下去的话,风险可是很大的。所以啊,他们也是出于谨慎的考虑,才决定给牛市来个紧急叫停的。 **六、股市投机太多,市场乱了套,得整顿!** 你知道吗,这股市中的投机行为也是很多的。这样一来呢,市场的正常秩序就被打乱了。监管部门一看,这不行啊,得整顿整顿!于是呢,他们就出手了,给牛市来了个紧急叫停,想要把市场给整顿好。 **七、部分行业估值过高,得回调回归合理!** 哎呀,这部分行业的估值也是过高了啊!与实际价值严重背离,这可不行啊。于是呢,监管部门就出手了,想要让这部分行业的估值回调一下,回归到合理的区间去。这样一来呢,牛市也就只能被迫叫停了。 **八、宏观政策调整影响资金流动性,牛市难维持!** 你知道吗,这宏观政策也是有所调整的啊!这样一来呢,资金的流动性就受到了影响。牛市想要维持下去的话,也是很难的啊!所以啊,监管部门也是出于无奈的考虑,才决定给牛市来个紧急叫停的。 **九、市场预期变了,投资者信心动摇,牛市失动力!** 哎呀,这市场预期也是变了啊!投资者的信心开始动摇了。这样一来呢,牛市也就失去了动力。监管部门一看,这不行啊,得赶紧出手!于是呢,他们就决定给牛市来个紧急叫停,想要重新提振投资者的信心。 **十、国际金融市场波动传导至国内,为防风险得叫停!** 最后啊,这国际金融市场也是有所波动的啊!这样一来呢,就对国内股市产生了传导效应。监管部门一看,这不行啊,为了防范风险呢,他们也是果断出手了!给牛市来了个紧急叫停。 好啦好啦,说了这么多原因呢,大家是不是也对这牛市的突然叫停有了更深入的了解了呢?其实啊,这股市的涨跌都是有其内在原因的。我们作为投资者呢,也要学会理性看待市场的波动哦! 那么在这里呢,我也想问问大家:你们觉得这次牛市的突然叫停是好事还是坏事呢?对于未来的股市走势你们又怎么看呢?欢迎大家在评论区留言互动哦!让我们一起探讨股市的奥秘吧! 还有啊,如果你觉得这篇文章写得还不错的话呢,也记得给我点个赞和关注哦!这样我就能为大家带来更多有趣、有深度的财经文章啦!期待与大家在财经的世界里相遇哦! 好啦好啦,今天的文章就到这里啦!希望大家都能理性看待股市的波动哦!我们下期再见啦!拜拜~" 119 | Main().handle(text, 3, './') 120 | # for index, value in enumerate(photo_list): 121 | # image_bytes = base64.b64decode(value) 122 | # image = Image.open(io.BytesIO(image_bytes)) 123 | # 124 | # image.save(f"./{index}.png") 125 | -------------------------------------------------------------------------------- /utils/server.py: -------------------------------------------------------------------------------- 1 | import socket, os, queue, json, time 2 | import subprocess 3 | import sys 4 | import random 5 | 6 | # 以下依赖不能删除,哪怕当前页面没有使用 7 | nas_path = os.path.dirname(__file__) 8 | helper_dir = os.path.dirname(os.path.abspath('__file__')) 9 | sys.path.insert(0, nas_path) 10 | parent_path = os.path.dirname(nas_path) 11 | sys.path.insert(0, parent_path) 12 | from sqlit_manage import * 13 | from get_hot_data import * 14 | from config import * 15 | from ai_tools import title_clas 16 | 17 | class Service(object): 18 | host = '127.0.0.1' 19 | port = 1288 20 | queue = queue.Queue() 21 | 22 | 23 | 24 | def __init__(self): 25 | self.service = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 | self.service.bind((self.host, self.port)) 27 | self.service.listen(5) 28 | time.sleep(2) 29 | 30 | def run(self): 31 | # self.net_start() 32 | # 获取本地账号信息 33 | result_list = get_all_data() 34 | # 生成任务,写入数据库 35 | for row in result_list: 36 | nickname, uid, classify, platform, task_num, status, create_date = row 37 | # 判断当前账号剩余配置任务 38 | publish_num = get_publish_num_today(uid) 39 | remaining_tasks = int(task_num) - publish_num 40 | if remaining_tasks< 0: 41 | remaining_tasks = 0 42 | print(f"账号:{nickname}---{uid},今天已配置:{publish_num},还剩余:{remaining_tasks}个任务。") 43 | if remaining_tasks > 0: 44 | # 翻页 45 | page_idx = 0 46 | while remaining_tasks > 0 and page_idx < 3: 47 | page_idx += 1 48 | # 获取热点信息,需要有随机性,当前只有抖音,网易,需要随机使用一种 49 | hot_platform = [wangyi] 50 | selected_function = random.choice(hot_platform) 51 | # 运行选中的函数 52 | print(f'获取最新热点信息...{page_idx}') 53 | hot_data = selected_function(page_idx) 54 | 55 | for item in hot_data: 56 | if classify != '全部': 57 | # 获取当前文章的分类 58 | try: 59 | cls = title_clas(item['article_info']) 60 | except: 61 | cls = '未知' 62 | else: 63 | cls = '全部' 64 | 65 | # 当前有缺陷,后期必须把任务上云,否则只能局部用户去重,无法全局去重,已经没有真正意义对文章去重 66 | repeat_flag = is_repeat(item['article_info'].replace(' ','')) 67 | if cls == classify and repeat_flag is False: 68 | print('当前热点符合条件!') 69 | # 符合条件插入数据库 70 | task_id = f"{uid}_{nickname}_{''.join(random.choices('0123456789', k=8))}" 71 | create_task_info(uid, task_id, item['article_info'].replace(' ',''), json.dumps(item['img_list']),platform) 72 | remaining_tasks -= 1 73 | else: 74 | print('当前热点不符合条件!') 75 | 76 | if remaining_tasks <= 0: 77 | break 78 | 79 | else: 80 | print(f"当前账号今天已发布:{publish_num},已全部完成!") 81 | 82 | # 获取资源 写入队列 83 | # 将任务转换为字典格式并放入队列 84 | tasks = load_tasks_to_queue() 85 | opted = [] 86 | opt = [] 87 | for task in tasks: 88 | if task[0] not in opted: 89 | task_dict = { 90 | 'uid': task[0], 91 | 'platform':task[1], 92 | 'task_id': task[2], 93 | 'content': task[3], 94 | 'img_url_list': task[4], 95 | 'status': task[5], 96 | 'publish_time': task[6], 97 | 'create_date': task[7] 98 | } 99 | opted.append(task[0]) 100 | opt.append(task[0]) 101 | self.queue.put(task_dict) 102 | print(f'获取任务数量:{len(opt)},其他等待下一轮') 103 | print("任务初始化完成,等待机器人工作...") 104 | while True: 105 | client, addr = self.service.accept() 106 | print(f'【客户端信息】...{addr}') 107 | if self.queue.empty(): 108 | rsp = { 109 | 'status': False, 110 | 'message': '当前无任务' 111 | } 112 | else: 113 | data = self.queue.get() 114 | rsp = { 115 | 'status': True, 116 | 'message': 'success', 117 | 'data': data 118 | } 119 | print(f'【发送数据】: {rsp}') 120 | client.send(json.dumps(rsp, ensure_ascii=False).encode('utf-8')) 121 | client.close() 122 | print('【发送完毕!!!】') 123 | print(f'-------------{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}-------------') 124 | if not rsp["status"]: 125 | break 126 | def start(self): 127 | current_file_path = os.path.abspath(__file__) 128 | # 构建命令 129 | command = ['cmd', '/c', 'start', 'cmd', '/k', 'python', current_file_path] 130 | # 运行命令并捕获输出 131 | subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, 132 | creationflags=subprocess.CREATE_NEW_CONSOLE) 133 | 134 | if __name__ == '__main__': 135 | os.system(f'title=中控服务') 136 | s = Service() 137 | 138 | while True: 139 | try: 140 | s.run() 141 | except Exception as e: 142 | print(f"Error: {e}") -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 |
2 |

AIMedia 🤖

3 | 4 |

5 | Stargazers 6 | Issues 7 | Forks 8 | License 9 |

10 |
11 |

简体中文 | English

12 |
13 | 14 |
15 |
16 | Automatically capture trending topics, auto-generate news, and automatically publish to major platforms. Fully automated AI-powered media software 17 |
18 |
19 | 20 | ## Features 🎯 21 | 22 | - [x] Supports **trending news capture**, automatically fetching trending news from major platforms 23 | - [x] Douyin Trending 24 | - [x] NetEase News 25 | - [ ] Weibo Trending 26 | - [x] Supports **AI-based auto-news creation**, with automatic publishing to various platforms 27 | - [x] Today’s Headlines 28 | - [ ] Little Red Book 29 | - [ ] WeChat Official Accounts 30 | - [ ] Baijia Platform 31 | - [x] For text-only content, AI generates images to increase originality and improve reader engagement. 32 | 33 | ### Future Plans 📅 34 | 35 | - [ ] Auto-generate videos for publishing across platforms 36 | 37 | ## Community & Discussions 💬 38 | 39 | 40 | 41 | ## Video Demonstration 📺 42 | 43 | Bilibili video link:https://www.bilibili.com/video/BV1oYSVYaEaa/?share_source=copy_web&vd_source=998582dcaa6c1a862619086e9dda59cb 44 | 45 | ## Requirements 📦 46 | 47 | - Minimum recommended: CPU with 4 cores or more, 8GB of RAM or more, GPU is not required 48 | - Windows 10 or above 49 | 50 | ## Quick Start 🚀 51 | 52 | Download the one-click startup package, extract, and use directly (avoid paths with **Chinese characters**, **special characters**, or **spaces**). 53 | 54 | ### Windows 55 | - Baidu Drive: https://pan.baidu.com/s/1YIV2avc_i5V8IcltWoFh1g Code: 99k1 56 | 57 | After downloading, first extract `venv.tar.gz` to the `venv` folder in the current directory. The structure should look like this: 58 | 59 | 60 | ``` 61 | AIMedia 62 | ├─venv 63 | ├─main.py 64 | ├─chrome 65 | ├─... 66 | ``` 67 | 68 | It is recommended to **double-click** `update.bat` first to update to the **latest code** (requires Git). Then right-click to **run as administrator** `webui.bat` to start. 69 | 70 | After startup, the browser will open automatically (if a blank page opens, try using **Chrome** or **Edge**). 71 | 72 | ### Other Systems 73 | 74 | Not supported; only Windows is supported. 75 | 76 | ## Installation & Deployment 📥 77 | 78 | ### Prerequisites 79 | 80 | - Avoid using **paths with Chinese characters** to prevent unforeseen issues. 81 | - Make sure your **network** is stable; VPN should be in "global traffic" mode. 82 | 83 | #### ① Clone the Code 84 | 85 | ```shell 86 | git clone https://github.com/Anning01/AIMedia.git 87 | ``` 88 | #### ② Edit the Configuration File 89 | 90 | - Copy the `config.py` file and name it `local_config.py`. 91 | - Configure `zhipu_aip_key` as specified in `config.py`. Enable the Stable Diffusion API if you need AI-generated images. 92 | 93 | ### Manual Deployment 📦 94 | 95 | > Video Tutorial 96 | 97 | - Full usage demonstration: Bilibili video link:https://www.bilibili.com/video/BV1oYSVYaEaa/?share_source=copy_web&vd_source=998582dcaa6c1a862619086e9dda59cb 98 | - How to deploy on Windows: In progress `(*>﹏<*)′~ 99 | 100 | #### ① Create a Virtual Environment (Conda) 101 | 102 | It is recommended to use [conda](https://www.anaconda.com/download/success) to create a Python virtual environment. 103 | 104 | ```shell 105 | git clone https://github.com/Anning01/AIMedia.git 106 | cd AIMedia 107 | conda create -n AIMedia python=3.12.4 108 | conda activate AIMedia 109 | pip install -r requirements.txt 110 | ``` 111 | 112 | #### ② Start the Web Interface 🌐 113 | 114 | Be sure to execute the following command in the AIMedia project `root directory`. 115 | 116 | ###### Windows 117 | 118 | ```bat 119 | conda activate AIMedia 120 | streamlit run main.py 121 | ``` 122 | #### ① Using venv (Ensure Python version 3.12.4) 123 | 124 | ```shell 125 | git clone https://github.com/Anning01/AIMedia.git 126 | cd AIMedia 127 | python -m venv venv 128 | .\venv\Scripts\activate 129 | pip install -r requirements.txt 130 | ``` 131 | 132 | #### ② Start the Web Interface 🌐 133 | 134 | Be sure to execute the following command in the AIMedia project `root directory`. 135 | 136 | ###### Windows 137 | 138 | ```bat 139 | streamlit run main.py 140 | Or 141 | .\webui.bat(not supported with Conda) 142 | ``` 143 | 144 | > Note:Our auto-publish feature depends on the Chrome beta version, which must be downloaded manually. 145 | 146 | Download link:: 147 | 148 | - Baidu Drive: Link::https://pan.baidu.com/s/1x6J3K4KdWrI9vOG8yvSSBw Code:7jyw 149 | 150 | After downloading and extracting the model, place the entire directory in `.\AIMedia`, 151 | so the final file path should look like this: `.\AIMedia\chrome`. 152 | 153 | ## Feedback & Suggestions 📢 154 | 155 | - You can submit an [issue](https://github.com/Anning01/AIMedia/issues) 156 | or a [pull request](https://github.com/Anning01/AIMedia/pulls). 157 | 158 | ## License 📝 159 | 160 | Click to view the [`LICENSE`](LICENSE) file. 161 | 162 | ## Star History 163 | 164 | [![Star History Chart](https://api.star-history.com/svg?repos=Anning01/AIMedia&type=Date)](https://star-history.com/#Anning01/AIMedia&Date) 165 | -------------------------------------------------------------------------------- /utils/client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # -*- coding:utf-8 -*- 3 | import os 4 | import socket 5 | import time 6 | import json 7 | import subprocess 8 | import random 9 | import string 10 | import sys 11 | import shutil 12 | import requests 13 | import importlib 14 | importlib.reload(sys) 15 | 16 | # 以下依赖不能删除,哪怕当前页面没有使用 17 | nas_path = os.path.dirname(__file__) 18 | helper_dir = os.path.dirname(os.path.abspath('__file__')) 19 | sys.path.insert(0, nas_path) 20 | parent_path = os.path.dirname(nas_path) 21 | sys.path.insert(0, parent_path) 22 | from sqlit_manage import * 23 | from get_hot_data import * 24 | from config import * 25 | from ai_tools import * 26 | from config import enable 27 | from utils.text_to_image import Main as StableDiffusion 28 | from auto_tools import AutoTools 29 | from sql_data import get_account_info 30 | 31 | 32 | class Client(object): 33 | token = os.environ.get('token') 34 | 35 | # 获取资源 36 | def get_task(self): 37 | pass 38 | 39 | # 图片处理 40 | def save_image_from_url(self,img_list, ids, content): 41 | root = f"temp/img_temp/{ids}" 42 | if not os.path.exists(root): 43 | os.makedirs(root) 44 | root = os.path.abspath(root) 45 | if len(img_list) > 0: 46 | if len(img_list) > 3: 47 | img_random = random.sample(img_list, 3) 48 | else: 49 | img_random = img_list 50 | for image_url in img_random: 51 | # 去水印 52 | image_url_new = del_watermark(image_url) 53 | save_path = rf"{root}/{img_list.index(image_url)}.jpg" 54 | num = 0 55 | while True: 56 | if not os.path.exists(save_path): 57 | print(f"图片{img_list.index(image_url)}正在处理中...") 58 | headers = { 59 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", 60 | } 61 | try: 62 | response = requests.get(image_url_new, headers=headers) 63 | if response.status_code == 200: 64 | with open(save_path, "wb") as file: 65 | file.write(response.content) 66 | except: 67 | pass 68 | else: 69 | break 70 | num += 1 71 | if num > 99: 72 | break 73 | else: 74 | if enable: 75 | print("图片生成...") 76 | StableDiffusion().handle(content, 3, root) 77 | 78 | return root 79 | 80 | def task(self,task_opt): 81 | # 获取当前日期和时间 82 | current_time = datetime.now() 83 | # 计算1小时前的时间 84 | task_opt['publish_time'] = datetime.strptime(task_opt['publish_time'], '%Y-%m-%d %H:%M:%S') 85 | time_difference = current_time - task_opt['publish_time'] 86 | if time_difference >= timedelta(hours=1): 87 | print("当前发布相隔大于1小时,可以发布") 88 | # # 生生文章 89 | print('---'*20) 90 | print('当前任务信息:') 91 | print(f'任务id:{task_opt["task_id"]}') 92 | print(f'发布平台:{task_opt["platform"]}') 93 | print('---' * 20) 94 | print("Ai正在生成文章...") 95 | article = hot2article(task_opt['content'],'') 96 | print(f'生成成功!!!') 97 | print("检查文章是否合格") 98 | if len(article) >500: 99 | print("文章符合条件") 100 | # 处理图片 101 | img_url_list = json.loads(task_opt['img_url_list']) 102 | imgs_path = self.save_image_from_url(img_url_list, task_opt["task_id"], article) 103 | # 获取登录权限 104 | base_url = "http://127.0.0.1:8000" 105 | headers = {"Content-Type": "application/json"} 106 | headers["Authorization"] = f"Bearer {self.token}" 107 | cookie = requests.get(f"{base_url}/user/account/", params=task_opt['uid'],headers=headers) 108 | cookie = cookie.json()[0]['cookie'] 109 | print('已获取登录权限') 110 | print('开始发布,请勿干预浏览器...') 111 | publish_tool = AutoTools() 112 | print(cookie, article, task_opt['platform'], imgs_path) 113 | result = publish_tool.publish(cookie, article, task_opt['platform'], imgs_path) 114 | if result["status"]: 115 | print('发布成功!!!') 116 | client_update(task_opt["task_id"], "已发布") 117 | print('跟新状态!!!') 118 | shutil.rmtree(imgs_path) 119 | print('清楚缓存!!!') 120 | else: 121 | print('处理失败,等待下一轮') 122 | else: 123 | print('文章不符合,等待重新配置') 124 | # 当前任务ai无法生成内容,删除该配置 125 | del_task_info_about_task_id(task_opt["task_id"]) 126 | else: 127 | print("当前账号发布时间未超过1小时") 128 | def run(self): 129 | host = '127.0.0.1' 130 | port = 1288 131 | client = socket.socket() 132 | try: 133 | client.connect((host, port)) 134 | except: 135 | print('请求异常....') 136 | time.sleep(5) 137 | return 138 | 139 | data = client.recv(10240).decode() 140 | data = json.loads(data) 141 | if not data['status']: 142 | print('当前暂无任务...') 143 | time.sleep(5) 144 | return 145 | 146 | task_opt = data['data'] 147 | # print(f'当前任务: {task}') 148 | self.task(task_opt) 149 | time.sleep(0.1) 150 | 151 | print('======================== 任务完成 ========================') 152 | 153 | 154 | if __name__ == '__main__': 155 | random_number = ''.join(random.choices(string.digits, k=4)) 156 | result = f"Ai机器人:{random_number}" 157 | os.system(f'title={result}') 158 | dw = Client() 159 | while (True): 160 | try: 161 | dw.run() 162 | except: 163 | pass 164 | -------------------------------------------------------------------------------- /pages/auto.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | 3 | from utils.local_storage import get_data 4 | from utils.sqlit_manage import * 5 | from utils.sql_data import get_login_account 6 | from st_pages import Page, Section, add_page_title 7 | from utils.auth import is_login 8 | 9 | from utils.client import Client 10 | from utils.server import Service 11 | import threading 12 | from utils.sqlit_manage import * 13 | import locale 14 | import subprocess 15 | 16 | 17 | # 初始化本地数据数据库 18 | 19 | create_database(create_conn()) 20 | set_beijin_time(create_conn()) 21 | 22 | 23 | 24 | # 保存配置--保存数据到本地sqlite 25 | @st.dialog('保存配置') 26 | def opt_vot(nickname,uid,classify,posting_cycle,platform): 27 | conn = create_conn() 28 | flag = create_task(nickname,uid,classify,posting_cycle,platform,conn) 29 | if flag: 30 | st.write('保存成功') 31 | else: 32 | st.write('保存失败') 33 | 34 | 35 | # 判断是否登录 36 | token = is_login() 37 | if not token: 38 | st.switch_page("main.py") 39 | else: 40 | st.session_state.token = token 41 | 42 | @st.cache_resource 43 | def start_server(): 44 | # 使用一个标志来确保任务只启动一次 45 | started = False 46 | def run_server(): 47 | nonlocal started 48 | if not started: 49 | started = True 50 | # s = Service() 51 | # s.start() 52 | current_path = os.path.dirname(__file__) 53 | parent_path = os.path.dirname(current_path) 54 | script_path = os.path.join(parent_path, "utils", "server.py") 55 | # 构建命令 56 | command = ['cmd', '/c', 'start', 'cmd', '/k', 'python', script_path] 57 | # 运行命令并捕获输出 58 | subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, 59 | creationflags=subprocess.CREATE_NEW_CONSOLE) 60 | threading.Thread(target=run_server).start() 61 | 62 | # 开始托管按钮 63 | st.write('AI托管中控服务,仅可开启一次') 64 | cols = st.columns([0.8, 0.1]) 65 | with cols[1]: 66 | if st.button("开启服务"): 67 | start_server() 68 | 69 | 70 | 71 | @st.cache_resource 72 | def start_client(): 73 | # 使用一个标志来确保任务只启动一次 74 | started = False 75 | 76 | token = get_data() 77 | def run_client(): 78 | nonlocal started 79 | if not started: 80 | started = True 81 | # 获取 token 82 | 83 | current_path = os.path.dirname(__file__) 84 | parent_path = os.path.dirname(current_path) 85 | script_path = os.path.join(parent_path, "utils", "client.py") 86 | 87 | # 构建命令行,将 token 作为环境变量传递 88 | # command = ['cmd', '/c', 'start', 'cmd', '/k', 'python', script_path] 89 | command = ['cmd', '/c', 'start', 'cmd', '/k', f'set token={token} && python {script_path}'] 90 | 91 | subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, 92 | creationflags=subprocess.CREATE_NEW_CONSOLE) 93 | started = False 94 | # 运行命令并捕获输出 95 | # try: 96 | # subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, 97 | # creationflags=subprocess.CREATE_NEW_CONSOLE, 98 | # encoding=locale.getpreferredencoding()) 99 | # 100 | # except UnicodeDecodeError as e: 101 | # subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, 102 | # creationflags=subprocess.CREATE_NEW_CONSOLE, errors='ignore') 103 | threading.Thread(target=run_client).start() 104 | 105 | st.write('托管机器人,可开启多个,建议开启绑定账号的数量,支持局域网多台机器自动联机,根据电脑cpu和自己的任务量') 106 | cols = st.columns([0.8, 0.1]) 107 | with cols[1]: 108 | if st.button("启动托管"): 109 | start_client() 110 | start_client.clear() 111 | 112 | 113 | 114 | 115 | start_server.clear() 116 | 117 | # 获取用户信息 118 | user_data = get_login_account() 119 | account_configs = [] 120 | for item in user_data: 121 | conn = create_conn() 122 | task_num,classify = select_task_num(item['uid'],conn) 123 | dict_ = { 124 | "nickname": item['nickname'], 125 | "uid": item['uid'], 126 | "classify": classify, 127 | "posting_cycle":task_num, 128 | "platform": item['platform'], 129 | } 130 | account_configs.append(dict_) 131 | 132 | # 页面布局 133 | st.title("AI 托管配置") 134 | # 根据搜索条件过滤账号配置 135 | search_query = st.text_input("搜索账号 (UID 或 昵称 或发布量)") 136 | posting_cycle_query = st.text_input("搜索发文周期") 137 | 138 | if search_query or posting_cycle_query: 139 | filtered_configs = [ 140 | config for config in account_configs 141 | if (search_query.lower() in config["uid"].lower() or search_query.lower() in config["nickname"].lower()) and 142 | (not posting_cycle_query or config["posting_cycle"] == posting_cycle_query) 143 | ] 144 | else: 145 | filtered_configs = account_configs 146 | 147 | 148 | # 账号分类选项 149 | account_categories = [ 150 | "全部", 151 | "美食", 152 | "旅行", 153 | "站内玩法", 154 | "话题互动", 155 | "娱乐", 156 | "社会", 157 | "二次元", 158 | "交通", 159 | "亲子", 160 | "体育", 161 | "军事", 162 | "剧情", 163 | "动物萌宠", 164 | "天气", 165 | "才艺", 166 | "文化教育", 167 | "时尚", 168 | "时政", 169 | "校园", 170 | "汽车", 171 | "游戏", 172 | "科技", 173 | "财经", 174 | ] 175 | # 发文数量选项 176 | posting_cycles = ["0", "3", "5", "7", "10"] 177 | 178 | 179 | # 展示账号配置列表 180 | if filtered_configs: 181 | cols = st.columns(6) 182 | cols[0].write("昵称") 183 | cols[1].write("UID") 184 | cols[2].write("账号分类") 185 | cols[3].write("发文周期") 186 | cols[4].write("发布平台") 187 | cols[5].write("保存配置") 188 | for i, config in enumerate(filtered_configs): 189 | cols = st.columns(6) 190 | cols[0].write(config["nickname"]) 191 | cols[1].write(config["uid"]) 192 | config["classify"] = cols[2].selectbox( 193 | f"选择分类", 194 | account_categories, 195 | index=account_categories.index(config["classify"]), 196 | key=f"classify_{i}" 197 | ) 198 | config["posting_cycle"] = cols[3].selectbox( 199 | f"每日发布", 200 | posting_cycles, 201 | index=posting_cycles.index(config["posting_cycle"]), 202 | key=f"posting_cycle_{i}" 203 | ) 204 | cols[4].write(config["platform"]) 205 | if cols[5].button("确认", key=f"get_{i}_{config['uid']}"): 206 | # 这里可以添加实际的删除账号逻辑 207 | opt_vot(config["nickname"],config["uid"],config["classify"],config["posting_cycle"],config["platform"]) 208 | # st.session_state.filtered_configs = filtered_configs 209 | else: 210 | st.warning("没有账号配置。") 211 | 212 | 213 | -------------------------------------------------------------------------------- /utils/sqlit_manage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | from datetime import datetime,timedelta 4 | 5 | 6 | # 连接到SQLite数据库(如果数据库不存在,会自动创建) 7 | def create_conn(): 8 | # 获取当前文件的绝对路径 9 | current_file_path = os.path.abspath(__file__) 10 | # 获取当前文件所在的目录 11 | current_directory = os.path.dirname(current_file_path) 12 | # 获取上级目录 13 | parent_directory = os.path.dirname(current_directory) 14 | db_path = os.path.join(parent_directory, 'article_task.db') 15 | conn = sqlite3.connect(db_path) 16 | return conn 17 | 18 | 19 | def create_database(conn): 20 | # 创建一个游标对象( 21 | cursor = conn.cursor() 22 | # 创建一个名为'task'的表,用于 23 | cursor.execute(''' 24 | CREATE TABLE IF NOT EXISTS tasks ( 25 | nickname TEXT, 26 | uid TEXT PRIMARY KEY, 27 | classify TEXT NOT NULL, 28 | platform TEXT NOT NULL, 29 | task_num INTEGER NOT NULL, 30 | status BOOLEAN DEFAULT FALSE, 31 | create_date DATETIME DEFAULT CURRENT_TIMESTAMP 32 | ) 33 | ''') 34 | # 创建 task_info 表 35 | cursor.execute(''' 36 | CREATE TABLE IF NOT EXISTS task_info ( 37 | uid TEXT NOT NULL, 38 | platform TEXT NOT NULL, 39 | task_id TEXT PRIMARY KEY, 40 | content TEXT NOT NULL, 41 | img_url_list TEXT NOT NULL, 42 | status TEXT NOT NULL, 43 | publish_time DATETIME DEFAULT '2024-01-01 00:00:00', 44 | create_date DATETIME DEFAULT CURRENT_TIMESTAMP, 45 | FOREIGN KEY (uid) REFERENCES tasks(uid) 46 | ) 47 | ''') 48 | # 提交事务 49 | conn.commit() 50 | cursor.close() 51 | 52 | 53 | # 创建触发器 54 | def set_beijin_time(conn): 55 | cursor = conn.cursor() 56 | # 创建触发器,将 create_date 列设置为北京时间 57 | cursor.execute(''' 58 | CREATE TRIGGER IF NOT EXISTS set_beijing_time 59 | AFTER INSERT ON task_info 60 | BEGIN 61 | UPDATE task_info 62 | SET create_date = DATETIME(create_date, '+8 hours') 63 | WHERE task_id = NEW.task_id; 64 | END; 65 | ''') 66 | conn.commit() 67 | cursor.close() 68 | conn.close() 69 | 70 | 71 | # 保持配置 72 | def create_task(nickname, uid, classify, posting_cycle, platform, conn): 73 | cursor = conn.cursor() 74 | # 跟新或者创建配置 75 | try: 76 | sql1 = f''' 77 | INSERT OR REPLACE INTO tasks (`nickname`, `uid`, `classify`, `platform`, `platform`,`task_num`) 78 | VALUES ('{nickname}','{uid}', '{classify}', '{platform}', '{platform}','{posting_cycle}') 79 | ''' 80 | print(sql1) 81 | cursor.execute(sql1) 82 | 83 | # 提交事务 84 | flag = True 85 | except sqlite3.Error as e: 86 | print(f"数据插入或更新失败: {e}") 87 | conn.rollback() 88 | flag = False 89 | # 删除原有已经配置的任务 90 | try: 91 | sql2 = f'''DELETE FROM task_info WHERE uid = '{uid}' ''' 92 | cursor.execute(sql2) 93 | except: 94 | print(f"删除失败: ") 95 | 96 | conn.commit() 97 | # 关闭连接 98 | conn.close() 99 | return flag 100 | 101 | 102 | # 查询发布量 103 | def select_task_num(uid, conn): 104 | cursor = conn.cursor() 105 | cursor.execute(''' 106 | SELECT task_num,classify FROM tasks WHERE uid = ? 107 | ''', (uid,)) 108 | result = cursor.fetchone() 109 | if result: 110 | task_num = f'{result[0]}' 111 | classify = f'{result[1]}' 112 | else: 113 | task_num = '0' 114 | classify = '全部' 115 | conn.close() 116 | return task_num, classify 117 | 118 | 119 | # 获取所有账号 120 | def get_all_data(): 121 | conn = create_conn() 122 | cursor = conn.cursor() 123 | cursor.execute("SELECT * FROM tasks") 124 | rows = cursor.fetchall() 125 | result_list = list(rows) 126 | return result_list 127 | 128 | 129 | # 获取当天账号已经配置任务数量 130 | def get_publish_num_today(uid): 131 | conn = create_conn() 132 | cursor = conn.cursor() 133 | # 获取当前日期 134 | current_date = datetime.now().strftime('%Y-%m-%d') 135 | 136 | # 查询指定 uid 的当天数据数量 137 | query = f""" 138 | SELECT COUNT(*) FROM task_info 139 | WHERE uid = {uid} AND DATE(create_date) = '{current_date}' 140 | """ 141 | cursor.execute(query) 142 | 143 | # 获取查询结果 144 | result = cursor.fetchone() 145 | 146 | # 打印查询结果 147 | count = result[0] 148 | # 关闭数据库连接 149 | conn.close() 150 | return count 151 | 152 | 153 | # 判断内容是否重复 154 | def is_repeat(content): 155 | conn = create_conn() 156 | cursor = conn.cursor() 157 | # 查询指定 content 的数据 158 | query = f""" 159 | SELECT EXISTS(SELECT 1 FROM task_info WHERE content = ?) 160 | """ 161 | cursor.execute(query, (content,)) 162 | 163 | # 获取查询结果 164 | result = cursor.fetchone() 165 | 166 | # 关闭数据库连接 167 | conn.close() 168 | 169 | # 检查查询结果是否为空 170 | if result is None: 171 | exists = False 172 | else: 173 | exists = bool(result[0]) 174 | return exists 175 | 176 | 177 | # 插入任务详情 178 | def create_task_info(uid, task_id, content, img_url_list, platform): 179 | conn = create_conn() 180 | cursor = conn.cursor() 181 | sql = f'''INSERT INTO task_info (uid, task_id, content, img_url_list, status, publish_time,platform) VALUES ('{uid}', '{task_id}', '{content}', '{img_url_list}', '已配置', '2023-01-01 00:00:00','{platform}')''' 182 | cursor.execute(sql) 183 | conn.commit() 184 | cursor.close() 185 | conn.close() 186 | 187 | 188 | def load_tasks_to_queue(): 189 | # 获取当前日期和时间 190 | current_date = datetime.now().strftime('%Y-%m-%d') 191 | current_time = datetime.now() 192 | 193 | # 计算1小时前的时间 194 | one_hour_ago = current_time - timedelta(hours=1) 195 | 196 | # 获取 task_info 表中的所有任务 197 | conn = create_conn() 198 | cursor = conn.cursor() 199 | 200 | # 执行查询,添加条件:publish_time 离当前时间大于1小时 201 | cursor.execute(''' 202 | SELECT * FROM task_info 203 | WHERE status != '已发布' 204 | AND DATE(create_date) = ? 205 | AND publish_time < ? 206 | ''', (current_date, one_hour_ago)) 207 | 208 | tasks = cursor.fetchall() 209 | cursor.close() 210 | conn.close() 211 | 212 | return tasks 213 | 214 | 215 | # 客户端跟新任务状态 216 | def client_update(task_id,status): 217 | conn = create_conn() 218 | # 创建游标对象 219 | cursor = conn.cursor() 220 | publish_time = datetime.now() 221 | # 执行更新操作 222 | 223 | cursor.execute(f'''UPDATE task_info SET status = '{status}',publish_time = '{publish_time}' WHERE task_id = '{task_id}' ''') 224 | 225 | conn.commit() 226 | cursor.close() 227 | 228 | 229 | # 根据task_id 删除任务 230 | def del_task_info_about_task_id(task_id): 231 | conn = create_conn() 232 | cursor = conn.cursor() 233 | # 删除原有已经配置的任务 234 | try: 235 | sql2 = f'''DELETE FROM task_info WHERE task_id = '{task_id}' ''' 236 | cursor.execute(sql2) 237 | except: 238 | print(f"删除失败: ") 239 | conn.commit() 240 | # 关闭连接 241 | conn.close() 242 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 木兰宽松许可证,第2版 2 | 3 | 木兰宽松许可证,第2版 4 | 5 | 2020年1月 http://license.coscl.org.cn/MulanPSL2 6 | 7 | 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 8 | 9 | 0. 定义 10 | 11 | “软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 12 | 13 | “贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 14 | 15 | “贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 16 | 17 | “法人实体” 是指提交贡献的机构及其“关联实体”。 18 | 19 | “关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是 20 | 指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 21 | 22 | 1. 授予版权许可 23 | 24 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可 25 | 以复制、使用、修改、分发其“贡献”,不论修改与否。 26 | 27 | 2. 授予专利许可 28 | 29 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定 30 | 撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡 31 | 献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软 32 | 件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“ 33 | 关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或 34 | 其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权 35 | 行动之日终止。 36 | 37 | 3. 无商标许可 38 | 39 | “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定 40 | 的声明义务而必须使用除外。 41 | 42 | 4. 分发限制 43 | 44 | 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“ 45 | 本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 46 | 47 | 5. 免责声明与责任限制 48 | 49 | “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对 50 | 任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于 51 | 何种法律理论,即使其曾被建议有此种损失的可能性。 52 | 53 | 6. 语言 54 | 55 | “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文 56 | 版为准。 57 | 58 | 条款结束 59 | 60 | 如何将木兰宽松许可证,第2版,应用到您的软件 61 | 62 | 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 63 | 64 | 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 65 | 66 | 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 67 | 68 | 3, 请将如下声明文本放入每个源文件的头部注释中。 69 | 70 | Copyright (c) [Year] [name of copyright holder] 71 | [Software Name] is licensed under Mulan PSL v2. 72 | You can use this software according to the terms and conditions of the Mulan 73 | PSL v2. 74 | You may obtain a copy of Mulan PSL v2 at: 75 | http://license.coscl.org.cn/MulanPSL2 76 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 77 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 78 | NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 79 | See the Mulan PSL v2 for more details. 80 | 81 | Mulan Permissive Software License,Version 2 82 | 83 | Mulan Permissive Software License,Version 2 (Mulan PSL v2) 84 | 85 | January 2020 http://license.coscl.org.cn/MulanPSL2 86 | 87 | Your reproduction, use, modification and distribution of the Software shall 88 | be subject to Mulan PSL v2 (this License) with the following terms and 89 | conditions: 90 | 91 | 0. Definition 92 | 93 | Software means the program and related documents which are licensed under 94 | this License and comprise all Contribution(s). 95 | 96 | Contribution means the copyrightable work licensed by a particular 97 | Contributor under this License. 98 | 99 | Contributor means the Individual or Legal Entity who licenses its 100 | copyrightable work under this License. 101 | 102 | Legal Entity means the entity making a Contribution and all its 103 | Affiliates. 104 | 105 | Affiliates means entities that control, are controlled by, or are under 106 | common control with the acting entity under this License, ‘control’ means 107 | direct or indirect ownership of at least fifty percent (50%) of the voting 108 | power, capital or other securities of controlled or commonly controlled 109 | entity. 110 | 111 | 1. Grant of Copyright License 112 | 113 | Subject to the terms and conditions of this License, each Contributor hereby 114 | grants to you a perpetual, worldwide, royalty-free, non-exclusive, 115 | irrevocable copyright license to reproduce, use, modify, or distribute its 116 | Contribution, with modification or not. 117 | 118 | 2. Grant of Patent License 119 | 120 | Subject to the terms and conditions of this License, each Contributor hereby 121 | grants to you a perpetual, worldwide, royalty-free, non-exclusive, 122 | irrevocable (except for revocation under this Section) patent license to 123 | make, have made, use, offer for sale, sell, import or otherwise transfer its 124 | Contribution, where such patent license is only limited to the patent claims 125 | owned or controlled by such Contributor now or in future which will be 126 | necessarily infringed by its Contribution alone, or by combination of the 127 | Contribution with the Software to which the Contribution was contributed. 128 | The patent license shall not apply to any modification of the Contribution, 129 | and any other combination which includes the Contribution. If you or your 130 | Affiliates directly or indirectly institute patent litigation (including a 131 | cross claim or counterclaim in a litigation) or other patent enforcement 132 | activities against any individual or entity by alleging that the Software or 133 | any Contribution in it infringes patents, then any patent license granted to 134 | you under this License for the Software shall terminate as of the date such 135 | litigation or activity is filed or taken. 136 | 137 | 3. No Trademark License 138 | 139 | No trademark license is granted to use the trade names, trademarks, service 140 | marks, or product names of Contributor, except as required to fulfill notice 141 | requirements in section 4. 142 | 143 | 4. Distribution Restriction 144 | 145 | You may distribute the Software in any medium with or without modification, 146 | whether in source or executable forms, provided that you provide recipients 147 | with a copy of this License and retain copyright, patent, trademark and 148 | disclaimer statements in the Software. 149 | 150 | 5. Disclaimer of Warranty and Limitation of Liability 151 | 152 | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY 153 | KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR 154 | COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT 155 | LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING 156 | FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO 157 | MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF 158 | THE POSSIBILITY OF SUCH DAMAGES. 159 | 160 | 6. Language 161 | 162 | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION 163 | AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF 164 | DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION 165 | SHALL PREVAIL. 166 | 167 | END OF THE TERMS AND CONDITIONS 168 | 169 | How to Apply the Mulan Permissive Software License,Version 2 170 | (Mulan PSL v2) to Your Software 171 | 172 | To apply the Mulan PSL v2 to your work, for easy identification by 173 | recipients, you are suggested to complete following three steps: 174 | 175 | i. Fill in the blanks in following statement, including insert your software 176 | name, the year of the first publication of your software, and your name 177 | identified as the copyright owner; 178 | 179 | ii. Create a file named "LICENSE" which contains the whole context of this 180 | License in the first directory of your software package; 181 | 182 | iii. Attach the statement to the appropriate annotated syntax at the 183 | beginning of each source file. 184 | 185 | Copyright (c) [Year] [name of copyright holder] 186 | [Software Name] is licensed under Mulan PSL v2. 187 | You can use this software according to the terms and conditions of the Mulan 188 | PSL v2. 189 | You may obtain a copy of Mulan PSL v2 at: 190 | http://license.coscl.org.cn/MulanPSL2 191 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 192 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 193 | NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 194 | See the Mulan PSL v2 for more details. 195 | -------------------------------------------------------------------------------- /pages/tasks.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os, shutil 3 | import time 4 | 5 | import requests 6 | import streamlit as st 7 | 8 | from config import enable 9 | from utils.auth import is_login 10 | from utils.sql_data import * 11 | from utils.ai_tools import hot2article, del_watermark 12 | from utils.auto_tools import AutoTools 13 | import random 14 | from datetime import datetime, timedelta 15 | from utils.text_to_image import Main as StableDiffusion 16 | 17 | # 判断是否登录 18 | token = is_login() 19 | if not token: 20 | st.switch_page("main.py") 21 | else: 22 | st.session_state.token = token 23 | 24 | 25 | def log_info(output_lines): 26 | # 初始化一个空的容器来显示打印台 27 | output_container = st.empty() 28 | # 清空容器内容 29 | output_container = output_container.empty() 30 | # 反转列表,使最新的内容在上方 31 | # reversed_lines = reversed(output_lines[-1:]) 32 | # output_lines = output_lines[-2:] 33 | # 将内容格式化为 HTML 34 | st.write(output_lines[-2:]) 35 | # output_html = "
".join(output_lines) 36 | # # 更新打印台 37 | # output_container.markdown(output_html, unsafe_allow_html=True) 38 | 39 | 40 | def save_image_from_url(img_list, ids, content): 41 | root = f"temp/img_temp/{ids}" 42 | if not os.path.exists(root): 43 | os.makedirs(root) 44 | root = os.path.abspath(root) 45 | if len(img_list) > 0: 46 | if len(img_list) > 3: 47 | img_random = random.sample(img_list, 3) 48 | else: 49 | img_random = img_list 50 | for image_url in img_random: 51 | # 去水印 52 | image_url_new = del_watermark(image_url) 53 | save_path = rf"{root}/{img_list.index(image_url)}.jpg" 54 | num = 0 55 | while True: 56 | # print("图片生成...") 57 | if not os.path.exists(save_path): 58 | # print("需要生成") 59 | headers = { 60 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", 61 | } 62 | try: 63 | response = requests.get(image_url_new, headers=headers) 64 | if response.status_code == 200: 65 | with open(save_path, "wb") as file: 66 | file.write(response.content) 67 | except: 68 | pass 69 | else: 70 | break 71 | num += 1 72 | if num > 99: 73 | break 74 | else: 75 | if enable: 76 | StableDiffusion().handle(content, 3, root) 77 | 78 | return root 79 | 80 | 81 | # 任务输出台 82 | @st.dialog("任务运行中,请勿关闭!!", width="small") 83 | def task_logs(): 84 | # # 暂时用单线程同步执行,后期优化 85 | # # 生成图片,后期添加 86 | # # 文案排版,后期添加 87 | # # 获取任务 88 | # for i in range(1, 4): 89 | i = 0 90 | while True: 91 | i += 1 92 | output_lines = [f"当前轮次:第{i}轮"] 93 | log_info(output_lines) 94 | task_opts = get_account_task(status="已发布") 95 | if len(task_opts) == 0: 96 | output_lines = [f"当前进度:任务已全部完成"] 97 | log_info(output_lines) 98 | st.rerun() 99 | random.shuffle(task_opts) 100 | for task_ev in task_opts: 101 | # 处理图片 102 | if task_ev["status"] == "已配置": 103 | output_lines = [ 104 | f"当前任务:账号:{task_ev['nickname']},热点:{task_ev['task_opt']}" 105 | ] 106 | output_lines.append(f"当前进度:AI正在生产文章...") 107 | log_info(output_lines) 108 | article = hot2article(task_ev["task_opt"], task_ev["classify"]) 109 | output_lines = [f"当前进度:文章生成成功..."] 110 | output_lines.append(f"文章:\n{article}") 111 | log_info(output_lines) 112 | if len(article) > 300: 113 | # 跟新数据库 114 | output_lines = [f"当前进度:跟新任务状态..."] 115 | log_info(output_lines) 116 | update_account_task(task_ev["id"], "已生产") 117 | # 生成任务详细数据 118 | output_lines.append(f"当前进度:保存任务详情..") 119 | log_info(output_lines) 120 | create_task_info( 121 | task_ev["nickname"], 122 | task_ev["uid"], 123 | task_ev["platform"], 124 | task_ev["classify"], 125 | article, 126 | task_ev["id"], 127 | ) 128 | output_lines = [f"当前进度:生完成!!"] 129 | log_info(output_lines) 130 | elif task_ev["status"] == "已生产": 131 | # 判断时间是否相差1小时 132 | now = datetime.now() 133 | # 获取当前账号最近发布时间 134 | old = get_last_time(task_ev["uid"]) 135 | is_difference = True 136 | # 如果第一次发布 137 | if old: 138 | # 判断是否相差1小时 139 | one_hour = timedelta(hours=1) 140 | time_difference = now - old 141 | if time_difference < one_hour: 142 | is_difference = False 143 | if is_difference: 144 | # 获取任务详情 145 | uid, content, platform = get_task_info(task_ev["id"]) 146 | output_lines = [f"当前任务:账号:{uid},发布平台:{platform}"] 147 | output_lines.append(f"当前进度:已获取文章...") 148 | log_info(output_lines) 149 | # 获取账号cookie 150 | cookie = get_account_info(uid) 151 | output_lines = [f"当前进度:已获取账号登录权限..."] 152 | log_info(output_lines) 153 | publish_tool = AutoTools() 154 | output_lines = [f"当前进度:等待浏览器,开始发布,请勿人工干预..."] 155 | log_info(output_lines) 156 | img_list = json.loads(task_ev["img_list"]) 157 | imgs_path = save_image_from_url(img_list, task_ev["id"], content) 158 | result = publish_tool.publish(cookie, content, platform, imgs_path) 159 | if result["status"]: 160 | update_account_task(task_ev["id"], "已发布") 161 | shutil.rmtree(imgs_path) 162 | else: 163 | output_lines = [f"当前进度:当前账号发布时间小于1小时,跳过"] 164 | log_info(output_lines) 165 | else: 166 | pass 167 | # 跟新数据 168 | time.sleep(6) 169 | 170 | 171 | # 页面布局 172 | st.title("今日任务") 173 | # 新增账号按钮 174 | cols = st.columns([0.8, 0.1]) 175 | with cols[1]: 176 | if st.button("启动任务"): 177 | task_logs() 178 | 179 | # 展示账号数据 180 | st.header("任务数据") 181 | platform_filter = st.selectbox("选择状态", ["全部", "已配置", "已生产", "已发布"]) 182 | task_data = get_account_task() 183 | tasks = [ 184 | task 185 | for task in task_data 186 | if platform_filter == "全部" or task["status"] == platform_filter 187 | ] 188 | 189 | # 展示任务列表 190 | if tasks: 191 | cols = st.columns(7) 192 | cols[0].write("昵称") 193 | cols[1].write("UID") 194 | cols[2].write("任务平台") 195 | cols[3].write("所属分类") 196 | cols[4].write("热点事件") 197 | cols[5].write("任务进度") 198 | cols[6].write("操作") 199 | for i, task in enumerate(tasks): 200 | cols = st.columns(7) 201 | cols[0].write(task["nickname"]) 202 | cols[1].write(task["uid"]) 203 | cols[2].write(task["platform"]) 204 | cols[3].write(task["classify"]) 205 | cols[4].write(task["task_opt"].split("\n")[0]) 206 | cols[5].write(task["status"]) 207 | if cols[6].button("取消任务", key=f"cancel_{i}"): 208 | # 这里可以添加实际的取消任务逻辑 209 | del_account_task(task["id"]) 210 | st.rerun() 211 | else: 212 | st.warning("没有任务数据。") 213 | -------------------------------------------------------------------------------- /utils/get_hot_data.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import datetime, timedelta 3 | import requests 4 | import json 5 | from lxml import etree 6 | 7 | 8 | def format_date(date): 9 | return date.strftime("%Y%m%d") 10 | 11 | 12 | def hot_data(category, page,sessionid): 13 | cag = { 14 | "全部": "", 15 | "美食": "&sentence_tag=9000", 16 | "旅行": "&sentence_tag=10000", 17 | "站内玩法": "&sentence_tag=1004,1000,1002,1003,1001", 18 | "话题互动": "&sentence_tag=20001,20006,20000,20003,20005,20002,20", 19 | "娱乐": "&sentence_tag=2007,2000,2011,2012,2009,2010,2004,2005,2003,2008,2001,2002,2006", 20 | "社会": "&sentence_tag=4005,4006,4007,4003,4004,4000", 21 | "二次元": "&sentence_tag=13000", 22 | "交通": "&sentence_tag=23000", 23 | "亲子": "&sentence_tag=19000", 24 | "体育": "&sentence_tag=5002,5000,5001", 25 | "军事": "&sentence_tag=21000", 26 | "剧情": "&sentence_tag=18000", 27 | "动物萌宠": "&sentence_tag=8000", 28 | "天气": "&sentence_tag=22001,22002", 29 | "才艺": "&sentence_tag=17000", 30 | "文化教育": "&sentence_tag=14000", 31 | "时尚": "&sentence_tag=16000", 32 | "时政": "&sentence_tag=3000,3001,3002", 33 | "校园": "&sentence_tag=15000", 34 | "汽车": "&sentence_tag=11000", 35 | "游戏": "&sentence_tag=12000,12001", 36 | "科技": "&sentence_tag=6000", 37 | "财经": "&sentence_tag=7000", 38 | } 39 | 40 | cookies = { 41 | "sessionid_douhot": sessionid, 42 | "sessionid_ss_douhot": sessionid, 43 | } 44 | headers = { 45 | "authority": "douhot.douyin.com", 46 | "accept": "application/json, text/plain, */*", 47 | "accept-language": "zh-CN,zh;q=0.9", 48 | "sec-ch-ua": '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"', 49 | "sec-ch-ua-mobile": "?0", 50 | "sec-ch-ua-platform": '"Windows"', 51 | "sec-fetch-dest": "empty", 52 | "sec-fetch-mode": "cors", 53 | "sec-fetch-site": "same-origin", 54 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", 55 | } 56 | # 获取当前日期 57 | today = datetime.now() 58 | 59 | # 获取昨天的日期 60 | yesterday = today - timedelta(days=1) 61 | 62 | # 格式化日期 63 | today_formatted = format_date(today) 64 | yesterday_formatted = format_date(yesterday) 65 | response = requests.get( 66 | f"https://douhot.douyin.com/douhot/v1/billboard/total?type=range&start_date={yesterday_formatted}&end_date={today_formatted}&page={page}&page_size=10{cag[category]}", 67 | cookies=cookies, 68 | headers=headers, 69 | ) 70 | data = [] 71 | i = 1 72 | for item in response.json()["data"]["objs"]: 73 | dict_ = { 74 | "sentence": item["sentence"], 75 | "sentence_id": item["sentence_id"], 76 | "sentence_tag_name": item["sentence_tag_name"], 77 | "hot_score": f'{round(int(item["hot_score"])/10000,2)}万', 78 | "video_count": item["video_count"], 79 | "num": i, 80 | } 81 | data.append(dict_) 82 | i += 1 83 | return data 84 | 85 | 86 | def hot_item(sentence_id, num,sessionid): 87 | cookies = { 88 | "sessionid_douhot": sessionid, 89 | "sessionid_ss_douhot": sessionid, 90 | } 91 | 92 | headers = { 93 | "authority": "douhot.douyin.com", 94 | "accept": "application/json, text/plain, */*", 95 | "accept-language": "zh-CN,zh;q=0.9", 96 | "sec-ch-ua": '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"', 97 | "sec-ch-ua-mobile": "?0", 98 | "sec-ch-ua-platform": '"Windows"', 99 | "sec-fetch-dest": "empty", 100 | "sec-fetch-mode": "cors", 101 | "sec-fetch-site": "same-origin", 102 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", 103 | } 104 | 105 | params = { 106 | "page": "1", 107 | "page_size": "30", 108 | } 109 | 110 | response = requests.get( 111 | f"https://douhot.douyin.com/douhot/v1/sentence/{sentence_id}/sentence_item", 112 | params=params, 113 | cookies=cookies, 114 | headers=headers, 115 | ) 116 | video_list = [] 117 | for item in response.json()["data"]["objs"]: 118 | item_duration = item["item_info"]["item_duration"] / 1000 119 | 120 | if item_duration >= 30 and item_duration <= 60 * 3: 121 | dict_ = { 122 | "url": item["item_info"]["item_url"], 123 | "cover": item["item_info"]["item_cover_url"], 124 | "title": item["item_info"]["item_title"], 125 | "id": item["item_info"]["item_id"], 126 | } 127 | video_list.append(dict_) 128 | if len(video_list) >= num: 129 | break 130 | return video_list 131 | 132 | 133 | def wangyi(page): 134 | if page == 1: 135 | url = "https://news.163.com/special/cm_yaowen20200213/?callback=data_callback" 136 | elif page == 10: 137 | url = f"https://news.163.com/special/cm_yaowen20200213_{page}/?callback=data_callback" 138 | else: 139 | url = f"https://news.163.com/special/cm_yaowen20200213_0{page}/?callback=data_callback" 140 | 141 | data = [] 142 | 143 | headers = { 144 | "accept": "*/*", 145 | "accept-language": "zh-CN,zh;q=0.9", 146 | # 'cookie': 'P_INFO=16602065002|1727678719|1|d90_client|00&99|null&null&null#gud&440100#10#0|&0|null|16602065002; _ntes_nuid=3ed19ddd4f140c77c8c3d4f50a726efa; _antanalysis_s_id=1729131629646; UserProvince=%u5168%u56FD; pver_n_f_l_n3=a; Hm_lvt_f8682ef0d24236cab0e9148c7b64de8a=1729133176; BAIDU_SSP_lcr=http://localhost:63342/; s_n_f_l_n3=115b86d864941cac1729140414621; Hm_lpvt_f8682ef0d24236cab0e9148c7b64de8a=1729144132; _ntes_origin_from=localhost%3A63342; ne_analysis_trace_id=1729148602678; vinfo_n_f_l_n3=115b86d864941cac.1.3.1729131629255.1729140047776.1729149045364', 147 | "referer": "https://news.163.com/", 148 | "sec-ch-ua": '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"', 149 | "sec-ch-ua-mobile": "?0", 150 | "sec-ch-ua-platform": '"Windows"', 151 | "sec-fetch-dest": "script", 152 | "sec-fetch-mode": "no-cors", 153 | "sec-fetch-site": "same-origin", 154 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", 155 | } 156 | params = { 157 | "callback": "data_callback", 158 | } 159 | response = requests.get(url, params=params, headers=headers) 160 | res = response.text.replace("data_callback(", "")[0:-1] 161 | data_str = json.loads(res.rstrip(",\n ]").strip() + "]") 162 | 163 | for item in data_str: 164 | title = item["title"] 165 | article_url = item["docurl"] 166 | cover_url = item["imgurl"] 167 | if "video" not in article_url: 168 | try: 169 | headers = { 170 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", 171 | } 172 | content = requests.get(article_url, headers=headers) 173 | content_html = etree.HTML(content.text) 174 | try: 175 | date_str = ( 176 | content_html.xpath('//*[@id="contain"]/div[2]/div[2]/text()')[0] 177 | .strip() 178 | .replace(" 来源:", "") 179 | ) 180 | except: 181 | date_str = ( 182 | content_html.xpath( 183 | '//*[@id="container"]/div[1]/div[2]/text()[1]' 184 | )[0] 185 | .strip() 186 | .replace(" 来源:", "") 187 | ) 188 | date_str = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") 189 | img_list = content_html.xpath('//*[@id="content"]/div[2]/p/img/@src') 190 | txt = content_html.xpath('//*[@id="content"]/div[2]/p') 191 | article_info = "" 192 | for item in txt: 193 | t = item.xpath(".//text()") 194 | for i in t: 195 | article_info += i 196 | article_info += "\n" 197 | if len(article_info) > 0 and "不得转载" not in article_info: 198 | dict_ = { 199 | "title": title, 200 | "article_url": article_url, 201 | "cover_url": cover_url, 202 | "date_str": date_str, 203 | "article_info": article_info, 204 | "img_list": img_list, 205 | } 206 | data.append(dict_) 207 | except: 208 | pass 209 | data_sort = sorted(data, key=lambda x: x["date_str"], reverse=True) 210 | return data_sort 211 | -------------------------------------------------------------------------------- /pages/hots.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | 3 | from manage.account import get_account_list 4 | from utils.auth import auth_level_expiry_time, is_login 5 | from utils.get_hot_data import hot_data, hot_item, wangyi 6 | import requests 7 | import threading 8 | from utils.sql_data import create_task 9 | from utils.auto_tools import AutoTools 10 | from utils.local_storage import * 11 | 12 | # 模拟数据 13 | def get_hot_data(category, page): 14 | try: 15 | sessionid_token = get_sessionid() 16 | except: 17 | sessionid_token = None 18 | if sessionid_token: 19 | data = hot_data(category, page,sessionid_token) 20 | return data,sessionid_token 21 | else: 22 | return None,None 23 | 24 | 25 | 26 | @st.dialog("任务配置") 27 | def vote(video_list, sentence, sentence_tag_name): 28 | # 获取账号数据 29 | # accounts = get_login_account() 30 | accounts = get_account_list() 31 | 32 | if not accounts: 33 | st.warning("没有可用的账号") 34 | return 35 | 36 | # 显示下拉框 37 | account_options = [ 38 | f"昵称:{account['nickname']},平台:{account['platform']},uid;{account['uid']}" 39 | for account in accounts 40 | ] 41 | selected_account_option = st.selectbox("选择账号", account_options) 42 | 43 | # 解析选择的账号 44 | selected_account_index = account_options.index(selected_account_option) 45 | selected_account = accounts[selected_account_index] 46 | # 点击确定按钮 47 | cols = st.columns([0.8, 0.2]) 48 | opt_status = None 49 | with cols[1]: 50 | if st.button("确定", use_container_width=True): 51 | img_list = [] 52 | # 请求接口进行配置 53 | opt_status = create_task( 54 | sentence_tag_name, sentence, selected_account, img_list 55 | ) 56 | if opt_status is True: 57 | srt_ = "配置成功" 58 | st.markdown(f"{srt_}", unsafe_allow_html=True) 59 | elif opt_status is False: 60 | srt_ = "您旗下已有或者已发布相同配置,为提高账号权重,无法为您重复配置" 61 | st.markdown(f"{srt_}", unsafe_allow_html=True) 62 | else: 63 | pass 64 | 65 | st.write(f"事件:{sentence},分类:{sentence_tag_name}") 66 | st.write(f"相关视频:") 67 | idx = 1 68 | for item in video_list: 69 | # st.write(f"url{idx}:{item['url']}") 70 | response = requests.get(item["url"]) 71 | st.video(response.content) 72 | idx += 1 73 | 74 | 75 | @st.dialog("任务配置") 76 | def vote_wangyi(video_list, sentence, img_list): 77 | # 获取账号数据 78 | # accounts = get_login_account(phone) 79 | accounts = get_account_list() 80 | 81 | if not accounts: 82 | st.warning("没有可用的账号") 83 | return 84 | 85 | # 显示下拉框 86 | account_options = [ 87 | f"昵称:{account['nickname']},平台:{account['platform']},uid;{account['uid']}" 88 | for account in accounts 89 | ] 90 | selected_account_option = st.selectbox("选择账号", account_options) 91 | 92 | # 解析选择的账号 93 | selected_account_index = account_options.index(selected_account_option) 94 | selected_account = accounts[selected_account_index] 95 | # 点击确定按钮 96 | cols = st.columns([0.8, 0.2]) 97 | opt_status = None 98 | task_opt = sentence + "\n" + video_list 99 | with cols[1]: 100 | if st.button("确定", use_container_width=True): 101 | # 请求接口进行配置 102 | opt_status = create_task("未知", task_opt, selected_account, img_list) 103 | if opt_status is True: 104 | srt_ = "配置成功" 105 | st.markdown(f"{srt_}", unsafe_allow_html=True) 106 | elif opt_status is False: 107 | srt_ = "您旗下已有或者已发布相同配置,为提高账号权重,无法为您重复配置" 108 | st.markdown(f"{srt_}", unsafe_allow_html=True) 109 | else: 110 | pass 111 | 112 | st.write(f"事件:{sentence}") 113 | st.write(f"相关素材:") 114 | for img in img_list: 115 | st.image(img, caption="示例图片", use_column_width=True) 116 | # st.write(img) 117 | 118 | 119 | def show_data(hot_data,sessionid_token): 120 | # 显示列名 121 | st.write("### 热点具体数据") 122 | warn_srt_ = "部分视频数据因不符合制作要求已经过滤,如视频中只有音乐,即使没有视频也不影响配置使用" 123 | st.markdown(f"{warn_srt_}", unsafe_allow_html=True) 124 | cols = st.columns( 125 | [0.05, 0.3, 0.2, 0.2, 0.15, 0.1], gap="medium", vertical_alignment="top" 126 | ) 127 | cols[0].write("排名") 128 | cols[1].write("热点事件") 129 | cols[2].write("热力值") 130 | cols[3].write("相关视频") 131 | cols[4].write("分类") 132 | cols[5].write("操作") 133 | 134 | # 显示每条数据 135 | for item in hot_data: 136 | cols = st.columns([0.05, 0.3, 0.2, 0.2, 0.14, 0.1]) 137 | cols[0].write(item["num"]) 138 | cols[1].write(item["sentence"]) 139 | cols[2].write(item["hot_score"]) 140 | 141 | # 相关视频按钮 142 | 143 | if cols[3].button( 144 | f"{item['video_count']}个视频", 145 | key=f"video_{item['num']}", 146 | use_container_width=True, 147 | ): 148 | pass 149 | # video_list = hot_item(item["sentence_id"], item["video_count"]) 150 | # # 每行展示4个视频 151 | # for i in range(0, len(video_list), 10): 152 | # row_cols = st.columns(10) 153 | # for j in range(10): 154 | # if i + j < len(video_list): 155 | # video = video_list[i + j] 156 | # with row_cols[j]: 157 | # response = requests.get(video["url"]) 158 | # st.video(response.content) 159 | 160 | cols[4].write(item["sentence_tag_name"]) 161 | 162 | # 配置按钮 163 | if cols[5].button("配置", key=f"config_{item['num']}", use_container_width=True): 164 | result, message = auth_level_expiry_time() 165 | if result: 166 | 167 | video_list = hot_item(item["sentence_id"], item["video_count"],sessionid_token) 168 | vote(video_list, item["sentence"], item["sentence_tag_name"]) 169 | else: 170 | st.write(message) 171 | 172 | 173 | def show_data_wangyi(hot_data): 174 | # 显示列名 175 | st.write("### 热点具体数据") 176 | cols = st.columns( 177 | [0.05, 0.6, 0.1, 0.15, 0.1], gap="medium", vertical_alignment="center" 178 | ) 179 | cols[0].write("排名") 180 | cols[1].write("热点事件") 181 | cols[2].write("查看文章") 182 | cols[3].write("发布日期") 183 | cols[4].write("操作") 184 | 185 | # 显示每条数据 186 | for item in hot_data: 187 | cols = st.columns([0.05, 0.6, 0.1, 0.15, 0.1], vertical_alignment="center") 188 | cols[0].write(hot_data.index(item) + 1) 189 | cols[1].write(item["title"]) 190 | 191 | # 相关视频按钮 192 | if cols[2].button( 193 | f"查看", 194 | key=f"video_{hot_data.index(item)+1}", 195 | use_container_width=True, 196 | ): 197 | st.write(item["article_info"]) 198 | 199 | cols[3].write(item["date_str"]) 200 | # 配置按钮 201 | if cols[4].button( 202 | "配置", key=f"config_{hot_data.index(item)}", use_container_width=True 203 | ): 204 | result, message = auth_level_expiry_time() 205 | if result: 206 | vote_wangyi(item["article_info"], item["title"], item["img_list"]) 207 | else: 208 | st.write(message) 209 | 210 | # @st.cache_resource 211 | @st.dialog("扫码登录") 212 | def get_scrt_douyin(): 213 | auto = AutoTools() 214 | cookies = auto.get_cookies_douy_hot('抖音热点宝') 215 | for item in cookies: 216 | if item['name'] == 'sessionid_douhot': 217 | sessionid = item['value'] 218 | save_data('sessionid_token',sessionid) 219 | st.rerun() 220 | 221 | 222 | 223 | def page_douyin(): 224 | # 页面布局 225 | # 头部:热点分类 226 | categories = [ 227 | "全部", 228 | "美食", 229 | "旅行", 230 | "站内玩法", 231 | "话题互动", 232 | "娱乐", 233 | "社会", 234 | "二次元", 235 | "交通", 236 | "亲子", 237 | "体育", 238 | "军事", 239 | "剧情", 240 | "动物萌宠", 241 | "天气", 242 | "才艺", 243 | "文化教育", 244 | "时尚", 245 | "时政", 246 | "校园", 247 | "汽车", 248 | "游戏", 249 | "科技", 250 | "财经", 251 | ] 252 | # 创建按钮 253 | category_buttons = [] 254 | cols = st.columns(12) # 每行12个按钮 255 | for i, category in enumerate(categories): 256 | with cols[i % 12]: 257 | category_buttons.append(st.button(category, use_container_width=True)) 258 | 259 | st.write("### 获取抖音密钥,网页打开后登录扫码") 260 | cols = st.columns([0.9, 0.1]) 261 | with cols[1]: 262 | if st.button("点击扫码"): 263 | get_scrt_douyin() 264 | 265 | # 获取选中的分类 266 | for i, button in enumerate(category_buttons): 267 | if button: 268 | st.session_state.selected_category = categories[i] 269 | st.session_state.page_ = 1 270 | st.rerun() 271 | break 272 | 273 | # 获取数据 274 | hot_data,sessionid_token = get_hot_data(st.session_state.selected_category, st.session_state.page_) 275 | if hot_data: 276 | show_data(hot_data,sessionid_token) 277 | else: 278 | st.write('请扫码') 279 | 280 | # 底部:页码 281 | page_count = 20 282 | page_cols = st.columns(page_count) # 每行10个按钮 283 | page_buttons = [] 284 | for i in range(1, 11): 285 | with page_cols[page_count - 11 + i]: 286 | page_buttons.append(st.button(str(i), use_container_width=True)) 287 | 288 | # 点击页码发起请求数据 289 | if any(page_buttons): 290 | selected_page = page_buttons.index(True) + 1 291 | st.session_state.page_ = selected_page 292 | st.rerun() 293 | 294 | 295 | def page_wangyi(): 296 | # 获取数据 297 | hot_data = wangyi(st.session_state.page_) 298 | show_data_wangyi(hot_data) 299 | 300 | # 底部:页码 301 | page_count = 20 302 | page_cols = st.columns(page_count) # 每行10个按钮 303 | page_buttons = [] 304 | for i in range(1, 11): 305 | with page_cols[page_count - 11 + i]: 306 | page_buttons.append(st.button(str(i), use_container_width=True)) 307 | 308 | # 点击页码发起请求数据 309 | if any(page_buttons): 310 | selected_page = page_buttons.index(True) + 1 311 | st.session_state.page_ = selected_page 312 | st.rerun() 313 | 314 | 315 | # 初始化session状态 316 | if "source_page" not in st.session_state: 317 | st.session_state.source_page = "热点库" 318 | if "page_" not in st.session_state: 319 | st.session_state.page_ = 1 320 | if "selected_category" not in st.session_state: 321 | st.session_state.selected_category = "全部" 322 | if "selected_platform" not in st.session_state: 323 | st.session_state.selected_platform = "抖音" 324 | 325 | 326 | # 判断是否登录 327 | token = is_login() 328 | if not token: 329 | st.switch_page("main.py") 330 | else: 331 | st.session_state.token = token 332 | 333 | # 创建标签栏 334 | platforms = ["抖音", "网易新闻"] 335 | st.session_state.selected_platform = st.sidebar.radio("选择平台", platforms) 336 | 337 | if st.session_state.selected_platform == "抖音": 338 | page_douyin() 339 | if st.session_state.selected_platform == "网易新闻": 340 | page_wangyi() 341 | -------------------------------------------------------------------------------- /utils/auto_tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import random 4 | import subprocess 5 | import time 6 | import psutil 7 | from selenium import webdriver 8 | from selenium.webdriver.common.by import By 9 | from selenium.webdriver.chrome.options import Options 10 | from webdriver_manager.chrome import ChromeDriverManager 11 | from selenium.webdriver.chrome.service import Service 12 | from selenium.webdriver.common.action_chains import ActionChains 13 | import pyperclip 14 | from selenium.webdriver.common.keys import Keys 15 | import pythoncom 16 | from win32com import client as win_client 17 | from PIL import Image 18 | from io import BytesIO 19 | import win32clipboard 20 | 21 | class AutoTools(object): 22 | def __init__(self): 23 | self.platform_url = {"头条号": "https://mp.toutiao.com/auth/page/login", 24 | "抖音热点宝":"https://douhot.douyin.com/welcome" 25 | } 26 | 27 | def get_driver(self): 28 | chrome_options = Options() 29 | current_path = os.path.dirname(__file__) 30 | parent_path = os.path.dirname(current_path) 31 | chrome_path = os.path.join(parent_path, "chrome", "chrome1.exe") 32 | pythoncom.CoInitialize() 33 | win_obj = win_client.Dispatch("Scripting.FileSystemObject") 34 | chrome_version = win_obj.GetFileVersion(chrome_path) 35 | chrome_version = chrome_version.strip() 36 | chrome_options.binary_location = chrome_path 37 | chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) 38 | driver = webdriver.Chrome( 39 | options=chrome_options, 40 | service=Service(ChromeDriverManager(chrome_version).install()), 41 | ) 42 | driver.execute_cdp_cmd( 43 | "Page.addScriptToEvaluateOnNewDocument", 44 | { 45 | "source": 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})' 46 | }, 47 | ) 48 | screen_width = driver.execute_script("return window.screen.width") 49 | screen_height = driver.execute_script("return window.screen.height") 50 | window_width = screen_width // 1.3 51 | window_height = screen_height 52 | driver.set_window_size(window_width, window_height) 53 | return driver 54 | 55 | def connect_driver(self, random_number, chrome_path,user_data_dir): 56 | chrome_options = Options() 57 | chrome_options.add_experimental_option( 58 | "debuggerAddress", f"127.0.0.1:{random_number}" 59 | ) 60 | pythoncom.CoInitialize() 61 | chrome_options.add_argument(fr"--user-data-dir={user_data_dir}") 62 | win_obj = win_client.Dispatch("Scripting.FileSystemObject") 63 | chrome_version = win_obj.GetFileVersion(chrome_path) 64 | chrome_version = chrome_version.strip() 65 | user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 66 | chrome_options.add_argument(f"user-agent={user_agent}") 67 | 68 | driver = webdriver.Chrome( 69 | options=chrome_options, 70 | service=Service(ChromeDriverManager(chrome_version).install()), 71 | ) 72 | return driver, random_number 73 | 74 | def run_as_admin(self, cmd): 75 | startupinfo = subprocess.STARTUPINFO() 76 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 77 | process = subprocess.Popen(cmd, shell=True, startupinfo=startupinfo) 78 | return process 79 | 80 | def get_cookies(self, platform): 81 | driver = self.get_driver() 82 | driver.get(self.platform_url[platform]) 83 | driver.delete_all_cookies() 84 | time.sleep(1) 85 | nickName, cookies, uid = None, None, None 86 | while True: 87 | try: 88 | time.sleep(1) 89 | nickName = driver.find_element("class name", "auth-avator-name").text 90 | cookies = driver.get_cookies() 91 | driver.find_element(By.LINK_TEXT, "设置").click() 92 | time.sleep(1) 93 | uid = driver.find_element( 94 | "xpath", 95 | '//*[@id="root"]/div/div[1]/div[1]/div[2]/div[5]/div/div[2]', 96 | ).text 97 | uid = uid.replace("复制ID", "").replace("\n", "").replace("\t", "") 98 | except: 99 | pass 100 | 101 | if nickName and cookies and uid: 102 | break 103 | return nickName, cookies, uid 104 | 105 | def get_cookies_douy_hot(self, platform): 106 | print("222") 107 | driver = self.get_driver() 108 | driver.get(self.platform_url[platform]) 109 | driver.delete_all_cookies() 110 | time.sleep(1) 111 | nickName, cookies, uid = None, None, None 112 | try: 113 | driver.find_element(By.XPATH, '//*[@id="root"]/section/header/div/div[2]/button').click() 114 | except: 115 | pass 116 | while True: 117 | try: 118 | time.sleep(1) 119 | cookies = driver.get_cookies() 120 | except: 121 | pass 122 | time.sleep(1) 123 | try: 124 | elm = driver.find_element(By.XPATH,'//*[@id="root"]/section/header/div/div[2]/div[2]/div/span/img') 125 | except: 126 | elm = None 127 | if elm: 128 | break 129 | return cookies 130 | 131 | def kill_process_tree(self, pid): 132 | try: 133 | parent = psutil.Process(pid) 134 | children = parent.children(recursive=True) 135 | for child in children: 136 | child.terminate() 137 | gone, still_alive = psutil.wait_procs(children, timeout=5) 138 | for p in still_alive: 139 | p.kill() 140 | parent.terminate() 141 | parent.wait(timeout=5) 142 | print(f"Process tree with root PID {pid} terminated successfully.") 143 | except psutil.NoSuchProcess: 144 | print(f"Process with PID {pid} does not exist.") 145 | except psutil.TimeoutExpired: 146 | print(f"Process with PID {pid} did not terminate in time. Forcefully killing...") 147 | parent.kill() 148 | print(f"Process with PID {pid} forcefully killed.") 149 | 150 | def publish(self, cookie, content, platform, imgs_path): 151 | urls = {"头条号": "https://mp.toutiao.com/profile_v4/graphic/publish"} 152 | url = urls[platform] 153 | current_path = os.path.dirname(__file__) 154 | parent_path = os.path.dirname(current_path) 155 | or_chrome_path = os.path.join(parent_path, "chrome", "chrome1.exe") 156 | random_number = random.randint(9000, 9999) 157 | new_chrome_path = os.path.join(parent_path, "chrome", f"chrome{random_number}.exe") 158 | try: 159 | shutil.copy(or_chrome_path, new_chrome_path) 160 | except: 161 | pass 162 | print("random_number:", random_number) 163 | user_data_dir = rf"C:\selenium\ChromeProfile{random_number}" 164 | cmd = rf'"{new_chrome_path}" --remote-debugging-port={random_number} --user-data-dir="{user_data_dir}" --start-maximized --no-first-run --no-default-browser-check' 165 | self.run_as_admin(cmd) 166 | driver, random_number = self.connect_driver(random_number, new_chrome_path,user_data_dir) 167 | driver.get(url) 168 | driver.delete_all_cookies() 169 | time.sleep(1) 170 | try: 171 | for ck in cookie: 172 | driver.add_cookie(ck) 173 | except Exception as e: 174 | print(e) 175 | time.sleep(5) 176 | lines = content.splitlines() 177 | title = lines[0].strip() 178 | title = title.replace("#", "") 179 | info = "\n".join(lines[1:]).strip() 180 | info = info.replace("#", "") 181 | is_copy_content = False 182 | out_flag = False 183 | publish_flag = False 184 | time_out = 60 * 2 185 | tart_time = time.time() 186 | imgs_list = os.listdir(imgs_path) 187 | imgs_list_len = len(imgs_list) 188 | result = { 189 | "status": False, 190 | "msg": "", 191 | } 192 | while True: 193 | try: 194 | if is_copy_content is False: 195 | textarea = driver.find_element( 196 | By.XPATH, 197 | '//*[@id="root"]/div/div[1]/div/div[1]/div[3]/div/div/div[2]/div/div/div/textarea', 198 | ) 199 | textarea.send_keys(title) 200 | time.sleep(random.randint(1, 2)) 201 | time.sleep(1) 202 | info_tag = driver.find_element( 203 | By.XPATH, 204 | '//*[@id="root"]/div/div[1]/div/div[1]/div[4]/div/div[1]', 205 | ) 206 | 207 | if imgs_list_len == 0: 208 | pyperclip.copy(info) 209 | info_tag.send_keys(Keys.CONTROL, "v") 210 | time.sleep(0.5) 211 | time.sleep(0.5) 212 | info_tag.send_keys(Keys.DOWN) 213 | is_copy_content = True 214 | else: 215 | info_len = info.split("\n") 216 | tep = int(len(info_len) / imgs_list_len) 217 | info1 = [i + "\n" for i in info_len[0:tep]] 218 | info2 = [i + "\n" for i in info_len[tep : tep * 2]] 219 | info3 = [i + "\n" for i in info_len[tep * 2 :]] 220 | ct = [info1, info2, info3] 221 | img_idx = 0 222 | for info_txt in ct: 223 | try: 224 | image = Image.open(os.path.join(imgs_path, imgs_list[img_idx])) 225 | output = BytesIO() 226 | image.save(output, "BMP") 227 | data = output.getvalue()[14:] 228 | output.close() 229 | win32clipboard.OpenClipboard() 230 | win32clipboard.EmptyClipboard() 231 | win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) 232 | win32clipboard.CloseClipboard() 233 | info_tag.send_keys(Keys.CONTROL, "v") 234 | time.sleep(0.5) 235 | info_tag.send_keys(Keys.DOWN) 236 | except: 237 | pass 238 | pyperclip.copy("".join(info_txt)) 239 | info_tag.send_keys(Keys.CONTROL, "v") 240 | time.sleep(0.5) 241 | info_tag.send_keys(Keys.DOWN) 242 | is_copy_content = True 243 | img_idx += 1 244 | except: 245 | pass 246 | 247 | print(is_copy_content, "is_copy_content") 248 | if is_copy_content: 249 | print("投放广告赚收益") 250 | tags = driver.find_elements(By.CLASS_NAME, "edit-input") 251 | for i in tags: 252 | time.sleep(1) 253 | txt = i.text 254 | if txt == "投放广告赚收益不投放广告": 255 | try: 256 | i.find_element(By.CLASS_NAME, "byte-radio-inner").click() 257 | except: 258 | pass 259 | if "头条首发" in txt: 260 | driver.execute_script("arguments[0].scrollIntoView();", i) 261 | if "授权平台自动维权" not in txt: 262 | try: 263 | i.find_element( 264 | By.CLASS_NAME, "byte-checkbox-wrapper" 265 | ).click() 266 | except: 267 | pass 268 | if "发布得更多收益" in txt: 269 | try: 270 | checked = i.find_element(By.CLASS_NAME, "combine-tip-wrap") 271 | child_elements = checked.find_element(By.TAG_NAME, "label") 272 | class_name = child_elements.get_attribute("class") 273 | if class_name == "byte-checkbox item-checkbox": 274 | i.find_element( 275 | By.CLASS_NAME, "byte-checkbox-wrapper" 276 | ).click() 277 | except: 278 | pass 279 | if "个人观点" in txt: 280 | try: 281 | source = i.find_element(By.CLASS_NAME, "source-info-wrap") 282 | labels = source.find_element( 283 | By.CLASS_NAME, "byte-checkbox-group" 284 | ) 285 | child_elements = labels.find_elements(By.TAG_NAME, "label") 286 | for info_e in child_elements: 287 | if info_e.text == "个人观点,仅供参考": 288 | class_name = info_e.get_attribute("class") 289 | if class_name == "byte-checkbox checkbot-item": 290 | info_e.find_element( 291 | By.CLASS_NAME, "byte-checkbox-wrapper" 292 | ).click() 293 | break 294 | except: 295 | pass 296 | if is_copy_content: 297 | if imgs_list_len == 0: 298 | time.sleep(1) 299 | tags = driver.find_elements(By.CLASS_NAME, "byte-tabs-header-title") 300 | for item in tags: 301 | for a in range(0, 5): 302 | if "1" in item.text: 303 | item.click() 304 | time.sleep(2) 305 | driver.find_element( 306 | By.XPATH, 307 | '//*[@id="root"]/div/div[2]/div[2]/div/div/div/div[2]/div/div[2]/div/div/div[2]/div/div/div[2]/ul/li[1]', 308 | ).click() 309 | publish_flag = True 310 | time.sleep(2) 311 | break 312 | elif "2" in item.text: 313 | item.click() 314 | time.sleep(2) 315 | driver.find_element( 316 | By.XPATH, 317 | '//*[@id="root"]/div/div[2]/div/div/div/div/div[2]/div/div[2]/div/div/div[2]/div/div[2]/div[2]/ul/li[1]', 318 | ).click() 319 | publish_flag = True 320 | time.sleep(2) 321 | break 322 | elif "内容建议" in item.text: 323 | item.click() 324 | driver.find_element( 325 | By.XPATH, 326 | '//*[@id="root"]/div/div[2]/div[2]/div/div/div/div[2]/div/div[2]/div/div/div[1]/div/a', 327 | ).click() 328 | time.sleep(5) 329 | else: 330 | pass 331 | else: 332 | publish_flag = True 333 | print("publish_flag:", publish_flag) 334 | if publish_flag: 335 | try: 336 | driver.find_element(By.CLASS_NAME, "publish-btn-last").click() 337 | time.sleep(3) 338 | driver.find_element(By.CLASS_NAME, "publish-btn-last").click() 339 | out_flag = True 340 | time.sleep(3) 341 | except: 342 | pass 343 | end_time = time.time() 344 | if out_flag: 345 | result["status"] = True 346 | break 347 | elif end_time - tart_time > time_out: 348 | break 349 | else: 350 | is_copy_content = False 351 | out_flag = False 352 | publish_flag = False 353 | driver.refresh() 354 | time.sleep(3) 355 | driver.quit() 356 | time.sleep(3) 357 | subprocess.call(['taskkill', '/F', '/IM', f'chrome{random_number}.exe']) 358 | try: 359 | time.sleep(3) 360 | shutil.rmtree(new_chrome_path) 361 | time.sleep(3) 362 | shutil.rmtree(user_data_dir) 363 | except: 364 | pass 365 | return result 366 | 367 | def get_acconut_data(self, cookie, platform): 368 | urls = {"头条号": "https://mp.toutiao.com/profile_v4/index"} 369 | driver = self.get_driver() 370 | driver.get(urls[platform]) 371 | driver.delete_all_cookies() 372 | try: 373 | for ck in cookie: 374 | driver.add_cookie(ck) 375 | except Exception as e: 376 | print(e) 377 | time.sleep(60 * 30) 378 | 379 | 380 | if __name__ == "__main__": 381 | a = AutoTools() 382 | 383 | --------------------------------------------------------------------------------