Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

9 changed files with 50 additions and 203 deletions

2
.gitignore vendored
View File

@ -6,4 +6,4 @@ __pycache__/
# config file
config.toml
uv.lock
poetry.lock

View File

@ -1,8 +0,0 @@
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <ybs0306748@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return Kinoshita Kenta
* ----------------------------------------------------------------------------
*/

View File

@ -1,50 +1,3 @@
# auto_login_EIP
太常要訂便當了,所以寫個自動登入 EIP 的東西
省的每次都要找 EIP 的網址是什麼
## env
* Python
* uv
* 剩下的 uv 會幫你裝
## repository structure
```text
./auto_login_EIP/
├── main.py # main file
├── config.toml # config file
├── utils/
├── pyproject.toml
├── .gitignore
└── README.md
```
## usage
### 配置
`config.toml` 設定好登入資訊,如果員工 ID 與密碼留空則會在登入時詢問你
專案路徑下有一個 `config.toml.example` 提供作為修改模板
### 執行
```shell
# uv run python main.py
```
或指定 config 檔
```shell
# uv run python main.py --config_path .\YOUR_CONFIG.toml
```
---
如果要酷一點的話串個 Windows 工作排程器或 Linux crontab
這樣每天早上就會自動幫你把 EIP 開起來,就能當個認真的模範打工仔了
我要繼續去 coding ㄌ各位88
`poetry run python -m main --config_path .\config.toml.example`

2
TODO.txt Normal file
View File

@ -0,0 +1,2 @@
1. 過久沒登入,然後重登之後,要操作的頁面被關掉
2. 重新一個新指令時,把其他開啟的頁面全部關掉

40
main.py
View File

@ -4,12 +4,10 @@
__author__ = 'kinoshitakenta'
__email__ = "ybs0306748@gmail.com"
import ctypes
import logging
import os
from pathlib import Path
from selenium.common.exceptions import InvalidSessionIdException
from utils.cli import cli
from utils.driver import get_driver
from utils.EIP_action import ActionType, Action
@ -27,50 +25,25 @@ def get_usage() -> list[tuple]:
return cmd_list
def clear_screen():
def display_usage():
if os.name == 'nt':
# Windows system
try:
# try to reset the text attributes
kernel32 = ctypes.windll.kernel32
handle = kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE
kernel32.SetConsoleTextAttribute(handle, 7)
except Exception:
pass
os.system('cls')
else:
# Unix-like system
clear_cmd = shutil.which('clear')
if clear_cmd:
os.system(clear_cmd)
else:
# backup plan: Use ANSI escape codes (to clear and reset the screen).
print('\033c', end='')
os.system('clear')
def display_usage():
clear_screen()
for cmd, msg in get_usage():
print(f"{cmd}: {msg}")
print()
def main(opt):
config_path = Path(opt.config_path)
if not config_path.exists() or not config_path.is_file():
print(f"Error: Config path '{config_path}' does not exist or is not a file.")
return
login_info = LoginInfo(config_path)
login_info = LoginInfo(Path(opt.config_path))
driver = get_driver()
keep_login_status(driver, login_info)
action_agent = Action(driver)
display_usage()
keep_login_status(driver, login_info)
try:
while True:
cmd = input("\nInput action code: ").strip()
@ -96,12 +69,9 @@ def main(opt):
continue
# * run command
if keep_login_status(driver, login_info):
keep_login_status(driver, login_info)
action_agent.run(action_code)
except InvalidSessionIdException:
print("Session has expired, please re-login.")
except KeyboardInterrupt:
pass

View File

@ -2,19 +2,19 @@
name = "auto-login-eip"
version = "0.1.0"
description = ""
authors = [{ name = "Kinoshita Kenta", email = "ybs0306748@gmail.com" }]
requires-python = ">=3.9"
authors = [
{name = "Your Name",email = "you@example.com"}
]
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"selenium (>=4.27.1,<5.0.0)",
"selenium (>=4.32.0,<5.0.0)",
"tomli (>=2.2.1,<3.0.0)",
"webdriver-manager (>=4.0.2,<5.0.0)",
"rich (>=13.9.4,<15.0.0)",
"rich (>=14.0.0,<15.0.0)"
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.uv]
package = false

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
# for parsing toml file
tomli
# `webdriver_manager` manages webdriver versions
webdriver_manager
selenium

View File

@ -14,7 +14,6 @@ class ActionType(IntEnum):
請假 = 1
補卡 = 2
訂便當 = 3
訂會議室 = 4
@classmethod
def has_value(cls, value):
@ -26,8 +25,7 @@ class Action():
self.action_list = {ActionType.登入: self.__登入,
ActionType.請假: self.__請假,
ActionType.補卡: self.__補卡,
ActionType.訂便當: self.__訂便當,
ActionType.訂會議室: self.__訂會議室
ActionType.訂便當: self.__訂便當
}
self.driver = driver
@ -127,17 +125,9 @@ class Action():
order_title, img, order_status = a_tag
if order_status.text != "【已訂購】":
for attempt in range(5):
try:
order_title.click()
open_page_num += 1
time.sleep(0.05)
break # 點成功就跳出 retry
except ElementClickInterceptedException:
print("點擊被遮蔽,重試中...")
time.sleep(0.2) # 等一下再重試
else:
print("點擊失敗:可能被遮蔽或其他原因")
if open_page_num > 0:
print(f"已開啟 {open_page_num} 個訂購頁面")
@ -145,16 +135,3 @@ class Action():
print("沒有尚未訂購的團購訂單")
self.driver.switch_to.default_content()
def __訂會議室(self):
"""訂會議室"""
self.driver.switch_to.frame(self.driver.find_element(By.ID, "main"))
all_meeting_room_tag = self.driver.find_element(By.ID, "WPPublicResource_TreeTagt0") # 會議室
ActionChains(self.driver).move_to_element(all_meeting_room_tag).move_to_element(all_meeting_room_tag).click(all_meeting_room_tag).perform()
Zhubei_tag = self.driver.find_element(By.ID, "WPPublicResource_TreeTagt1") # 竹北
ActionChains(self.driver).move_to_element(Zhubei_tag).move_to_element(Zhubei_tag).click(Zhubei_tag).perform()
meeting_room_500_tag = self.driver.find_element(By.ID, "WPPublicResource_TreeTagt2") # 500會議室
ActionChains(self.driver).move_to_element(meeting_room_500_tag).move_to_element(meeting_room_500_tag).click(meeting_room_500_tag).perform()

View File

@ -1,35 +1,17 @@
import getpass
import sys
import time
from pathlib import WindowsPath
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
import tomli
from selenium import webdriver
from selenium.common.exceptions import NoAlertPresentException, NoSuchWindowException, UnexpectedAlertPresentException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
# Constants for configuration and element IDs
EIP_URL = "https://eip.techmation.com.tw/MotorWeb/MotorFaceDefaultNew.aspx"
LANG_DROPDOWN_ID = "ddlLang"
LOGIN_ID_INPUT_ID = "txtLoginID"
LOGIN_PWD_INPUT_ID = "txtLoginPwd"
COMPANY_ID_DROPDOWN_ID = "ddlCompanyID"
LOGIN_BUTTON_ID = "btnLogin"
class LoginInfo():
def __init__(self, config_path: WindowsPath):
with open(config_path, mode="rb") as f:
config = tomllib.load(f)
required_fields = ["lang", "login_ID", "login_passwd", "company_ID"]
for field in required_fields:
if field not in config.get("login_info", {}):
raise ValueError(f"Missing required field: {field} in login_info section")
config = tomli.load(f)
self.lang = config["login_info"]["lang"]
self.login_ID = config["login_info"]["login_ID"]
@ -45,21 +27,8 @@ class LoginInfo():
"please enter your passwd: ")
def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo) -> bool:
"""
Attempt to log into the CHI MotorWeb ERP system using the provided login information.
This function navigates to the login page, fills in the login form with the user's credentials,
and attempts to log in. It handles unexpected alert pop-ups that indicate login failure,
and manages browser windows to ensure only the relevant page remains open.
Args:
driver (webdriver.Chrome): An instance of Selenium WebDriver controlling a Chrome browser.
login_info (LoginInfo): A data object containing login credentials and preferences.
Returns:
bool: True if login is successful or already logged in; False if login failed (e.g., due to incorrect credentials).
"""
def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo):
EIP_url = "https://eip.techmation.com.tw/MotorWeb/MotorFaceDefaultNew.aspx"
# * Close all windows except the main window.
while len(driver.window_handles) > 1:
@ -69,79 +38,57 @@ def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo) -> bool:
driver.switch_to.window(driver.window_handles[0])
top_page = driver.current_window_handle
driver.get(EIP_URL)
driver.get(EIP_url)
time.sleep(0.3)
if driver.title == "CHI MOTOR WEB ERP 登入":
# * Fill in all login information.
dropdown_element = driver.find_element(By.ID, LANG_DROPDOWN_ID)
dropdown_element = driver.find_element(By.ID, "ddlLang")
select = Select(dropdown_element)
select.select_by_value(login_info.lang)
input_text_element = driver.find_element(By.ID, LOGIN_ID_INPUT_ID)
input_text_element = driver.find_element(By.ID, "txtLoginID")
input_text_element.clear()
input_text_element.send_keys(login_info.login_ID)
input_text_element = driver.find_element(By.ID, LOGIN_PWD_INPUT_ID)
input_text_element = driver.find_element(By.ID, "txtLoginPwd")
input_text_element.clear()
input_text_element.send_keys(login_info.login_passwd)
dropdown_element = driver.find_element(By.ID, COMPANY_ID_DROPDOWN_ID)
dropdown_element = driver.find_element(By.ID, "ddlCompanyID")
select = Select(dropdown_element)
select.select_by_value(login_info.company_ID)
# * Press the submit button.
submit_btn = driver.find_element(By.ID, LOGIN_BUTTON_ID)
submit_btn = driver.find_element(By.ID, "btnLogin")
submit_btn.click()
time.sleep(1)
# * Check if login failed (alert popup)
try:
alert = driver.switch_to.alert
print(f"Login error message: {alert.text}")
alert.accept()
return False # Skip remaining logic, login failed
except (NoAlertPresentException, NoSuchWindowException):
pass # No alert, proceed
time.sleep(2)
time.sleep(3)
# * If login has pop a new window, switch main window to the new one.
login_page_handle = ""
main_page_handle = ""
for handle in driver.window_handles:
driver.switch_to.window(handle)
try:
title = driver.title
except UnexpectedAlertPresentException:
try:
alert = driver.switch_to.alert
print(f"Unexpected alert: {alert.text}")
alert.accept()
continue
except (NoAlertPresentException, NoSuchWindowException):
continue
if "CHI MotorWeb - " in title:
if "CHI MotorWeb - " in driver.title:
main_page_handle = handle
elif "CHI MOTOR WEB ERP 登入" in title:
elif "CHI MOTOR WEB ERP 登入" in driver.title:
login_page_handle = handle
stay_page_handle = main_page_handle or login_page_handle or top_page
# * get the page handle that should be stay
if main_page_handle:
stay_page_handle = main_page_handle
elif login_page_handle:
stay_page_handle = login_page_handle
else:
stay_page_handle = top_page
# * close unnecessary pages
for handle in driver.window_handles:
if handle != stay_page_handle:
try:
driver.switch_to.window(handle)
driver.close()
except Exception as e:
print(f"Error closing window {handle}: {e}")
driver.switch_to.window(stay_page_handle)
driver.maximize_window()
return True
elif "CHI MotorWeb - " in driver.title:
return True