forked from kinoshitakenta/auto_login_EIP
Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
|
f9792b99e0 | |
|
42ca15bfbd | |
|
82fb61ec26 | |
|
982e70a4e4 | |
|
e1ec580ce1 | |
|
5b87c86223 | |
|
2c5ca3c3d3 | |
|
86bd790f1c | |
|
ccd5dee807 | |
|
bee7927ef9 | |
|
8579b135cf | |
|
07e01554d0 | |
|
953b6ab909 | |
|
77054f1d84 | |
|
6523fecb5a | |
|
3c23ee2ce9 | |
|
0671d49497 | |
|
e32063e2ce | |
|
cfd1e5e61a | |
|
ef204f1aae | |
|
6de63bfdb5 |
|
@ -6,4 +6,4 @@ __pycache__/
|
||||||
|
|
||||||
# config file
|
# config file
|
||||||
config.toml
|
config.toml
|
||||||
poetry.lock
|
uv.lock
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* "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
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
49
README.md
49
README.md
|
@ -1,3 +1,50 @@
|
||||||
# auto_login_EIP
|
# auto_login_EIP
|
||||||
|
|
||||||
`poetry run python -m main --config_path .\config.toml.example`
|
太常要訂便當了,所以寫個自動登入 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
|
||||||
|
|
42
main.py
42
main.py
|
@ -4,10 +4,12 @@
|
||||||
__author__ = 'kinoshitakenta'
|
__author__ = 'kinoshitakenta'
|
||||||
__email__ = "ybs0306748@gmail.com"
|
__email__ = "ybs0306748@gmail.com"
|
||||||
|
|
||||||
|
import ctypes
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from selenium.common.exceptions import InvalidSessionIdException
|
||||||
from utils.cli import cli
|
from utils.cli import cli
|
||||||
from utils.driver import get_driver
|
from utils.driver import get_driver
|
||||||
from utils.EIP_action import ActionType, Action
|
from utils.EIP_action import ActionType, Action
|
||||||
|
@ -25,25 +27,50 @@ def get_usage() -> list[tuple]:
|
||||||
return cmd_list
|
return cmd_list
|
||||||
|
|
||||||
|
|
||||||
def display_usage():
|
def clear_screen():
|
||||||
if os.name == 'nt':
|
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')
|
os.system('cls')
|
||||||
else:
|
else:
|
||||||
os.system('clear')
|
# 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='')
|
||||||
|
|
||||||
|
|
||||||
|
def display_usage():
|
||||||
|
clear_screen()
|
||||||
for cmd, msg in get_usage():
|
for cmd, msg in get_usage():
|
||||||
print(f"{cmd}: {msg}")
|
print(f"{cmd}: {msg}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def main(opt):
|
def main(opt):
|
||||||
login_info = LoginInfo(Path(opt.config_path))
|
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)
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
keep_login_status(driver, login_info)
|
|
||||||
action_agent = Action(driver)
|
action_agent = Action(driver)
|
||||||
|
|
||||||
display_usage()
|
display_usage()
|
||||||
|
|
||||||
|
keep_login_status(driver, login_info)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
cmd = input("\nInput action code: ").strip()
|
cmd = input("\nInput action code: ").strip()
|
||||||
|
@ -69,8 +96,11 @@ def main(opt):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# * run command
|
# * run command
|
||||||
keep_login_status(driver, login_info)
|
if keep_login_status(driver, login_info):
|
||||||
action_agent.run(action_code)
|
action_agent.run(action_code)
|
||||||
|
|
||||||
|
except InvalidSessionIdException:
|
||||||
|
print("Session has expired, please re-login.")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
name = "auto-login-eip"
|
name = "auto-login-eip"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [
|
authors = [{ name = "Kinoshita Kenta", email = "ybs0306748@gmail.com" }]
|
||||||
{name = "Your Name",email = "you@example.com"}
|
requires-python = ">=3.9"
|
||||||
]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"selenium (>=4.32.0,<5.0.0)",
|
"selenium (>=4.27.1,<5.0.0)",
|
||||||
"tomli (>=2.2.1,<3.0.0)",
|
"tomli (>=2.2.1,<3.0.0)",
|
||||||
"webdriver-manager (>=4.0.2,<5.0.0)",
|
"webdriver-manager (>=4.0.2,<5.0.0)",
|
||||||
"rich (>=14.0.0,<15.0.0)"
|
"rich (>=13.9.4,<15.0.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
package = false
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
# for parsing toml file
|
|
||||||
tomli
|
|
||||||
|
|
||||||
# `webdriver_manager` manages webdriver versions
|
|
||||||
webdriver_manager
|
|
||||||
selenium
|
|
|
@ -14,6 +14,7 @@ class ActionType(IntEnum):
|
||||||
請假 = 1
|
請假 = 1
|
||||||
補卡 = 2
|
補卡 = 2
|
||||||
訂便當 = 3
|
訂便當 = 3
|
||||||
|
訂會議室 = 4
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_value(cls, value):
|
def has_value(cls, value):
|
||||||
|
@ -25,7 +26,8 @@ class Action():
|
||||||
self.action_list = {ActionType.登入: self.__登入,
|
self.action_list = {ActionType.登入: self.__登入,
|
||||||
ActionType.請假: self.__請假,
|
ActionType.請假: self.__請假,
|
||||||
ActionType.補卡: self.__補卡,
|
ActionType.補卡: self.__補卡,
|
||||||
ActionType.訂便當: self.__訂便當
|
ActionType.訂便當: self.__訂便當,
|
||||||
|
ActionType.訂會議室: self.__訂會議室
|
||||||
}
|
}
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
|
@ -125,9 +127,17 @@ class Action():
|
||||||
|
|
||||||
order_title, img, order_status = a_tag
|
order_title, img, order_status = a_tag
|
||||||
if order_status.text != "【已訂購】":
|
if order_status.text != "【已訂購】":
|
||||||
order_title.click()
|
for attempt in range(5):
|
||||||
open_page_num += 1
|
try:
|
||||||
time.sleep(0.05)
|
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:
|
if open_page_num > 0:
|
||||||
print(f"已開啟 {open_page_num} 個訂購頁面")
|
print(f"已開啟 {open_page_num} 個訂購頁面")
|
||||||
|
@ -135,3 +145,16 @@ class Action():
|
||||||
print("沒有尚未訂購的團購訂單")
|
print("沒有尚未訂購的團購訂單")
|
||||||
|
|
||||||
self.driver.switch_to.default_content()
|
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()
|
||||||
|
|
|
@ -1,17 +1,35 @@
|
||||||
import getpass
|
import getpass
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import WindowsPath
|
from pathlib import WindowsPath
|
||||||
|
|
||||||
import tomli
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib
|
||||||
|
else:
|
||||||
|
import tomli as tomllib
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
|
from selenium.common.exceptions import NoAlertPresentException, NoSuchWindowException, UnexpectedAlertPresentException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support.ui import Select
|
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():
|
class LoginInfo():
|
||||||
def __init__(self, config_path: WindowsPath):
|
def __init__(self, config_path: WindowsPath):
|
||||||
with open(config_path, mode="rb") as f:
|
with open(config_path, mode="rb") as f:
|
||||||
config = tomli.load(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")
|
||||||
|
|
||||||
self.lang = config["login_info"]["lang"]
|
self.lang = config["login_info"]["lang"]
|
||||||
self.login_ID = config["login_info"]["login_ID"]
|
self.login_ID = config["login_info"]["login_ID"]
|
||||||
|
@ -27,8 +45,21 @@ class LoginInfo():
|
||||||
"please enter your passwd: ")
|
"please enter your passwd: ")
|
||||||
|
|
||||||
|
|
||||||
def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo):
|
def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo) -> bool:
|
||||||
EIP_url = "https://eip.techmation.com.tw/MotorWeb/MotorFaceDefaultNew.aspx"
|
"""
|
||||||
|
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).
|
||||||
|
"""
|
||||||
|
|
||||||
# * Close all windows except the main window.
|
# * Close all windows except the main window.
|
||||||
while len(driver.window_handles) > 1:
|
while len(driver.window_handles) > 1:
|
||||||
|
@ -38,57 +69,79 @@ def keep_login_status(driver: webdriver.Chrome, login_info: LoginInfo):
|
||||||
driver.switch_to.window(driver.window_handles[0])
|
driver.switch_to.window(driver.window_handles[0])
|
||||||
|
|
||||||
top_page = driver.current_window_handle
|
top_page = driver.current_window_handle
|
||||||
driver.get(EIP_url)
|
driver.get(EIP_URL)
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
if driver.title == "CHI MOTOR WEB ERP 登入":
|
if driver.title == "CHI MOTOR WEB ERP 登入":
|
||||||
# * Fill in all login information.
|
# * Fill in all login information.
|
||||||
dropdown_element = driver.find_element(By.ID, "ddlLang")
|
dropdown_element = driver.find_element(By.ID, LANG_DROPDOWN_ID)
|
||||||
select = Select(dropdown_element)
|
select = Select(dropdown_element)
|
||||||
select.select_by_value(login_info.lang)
|
select.select_by_value(login_info.lang)
|
||||||
|
|
||||||
input_text_element = driver.find_element(By.ID, "txtLoginID")
|
input_text_element = driver.find_element(By.ID, LOGIN_ID_INPUT_ID)
|
||||||
input_text_element.clear()
|
input_text_element.clear()
|
||||||
input_text_element.send_keys(login_info.login_ID)
|
input_text_element.send_keys(login_info.login_ID)
|
||||||
|
|
||||||
input_text_element = driver.find_element(By.ID, "txtLoginPwd")
|
input_text_element = driver.find_element(By.ID, LOGIN_PWD_INPUT_ID)
|
||||||
input_text_element.clear()
|
input_text_element.clear()
|
||||||
input_text_element.send_keys(login_info.login_passwd)
|
input_text_element.send_keys(login_info.login_passwd)
|
||||||
|
|
||||||
dropdown_element = driver.find_element(By.ID, "ddlCompanyID")
|
dropdown_element = driver.find_element(By.ID, COMPANY_ID_DROPDOWN_ID)
|
||||||
select = Select(dropdown_element)
|
select = Select(dropdown_element)
|
||||||
select.select_by_value(login_info.company_ID)
|
select.select_by_value(login_info.company_ID)
|
||||||
|
|
||||||
# * Press the submit button.
|
# * Press the submit button.
|
||||||
submit_btn = driver.find_element(By.ID, "btnLogin")
|
submit_btn = driver.find_element(By.ID, LOGIN_BUTTON_ID)
|
||||||
submit_btn.click()
|
submit_btn.click()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
time.sleep(3)
|
# * 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)
|
||||||
|
|
||||||
# * If login has pop a new window, switch main window to the new one.
|
|
||||||
login_page_handle = ""
|
login_page_handle = ""
|
||||||
main_page_handle = ""
|
main_page_handle = ""
|
||||||
|
|
||||||
for handle in driver.window_handles:
|
for handle in driver.window_handles:
|
||||||
driver.switch_to.window(handle)
|
driver.switch_to.window(handle)
|
||||||
if "CHI MotorWeb - " in driver.title:
|
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:
|
||||||
main_page_handle = handle
|
main_page_handle = handle
|
||||||
elif "CHI MOTOR WEB ERP 登入" in driver.title:
|
elif "CHI MOTOR WEB ERP 登入" in title:
|
||||||
login_page_handle = handle
|
login_page_handle = handle
|
||||||
|
|
||||||
# * get the page handle that should be stay
|
stay_page_handle = main_page_handle or login_page_handle or top_page
|
||||||
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
|
# * close unnecessary pages
|
||||||
for handle in driver.window_handles:
|
for handle in driver.window_handles:
|
||||||
if handle != stay_page_handle:
|
if handle != stay_page_handle:
|
||||||
driver.switch_to.window(handle)
|
try:
|
||||||
driver.close()
|
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.switch_to.window(stay_page_handle)
|
||||||
driver.maximize_window()
|
driver.maximize_window()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif "CHI MotorWeb - " in driver.title:
|
||||||
|
return True
|
||||||
|
|
Loading…
Reference in New Issue