-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__ | ||
venv | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
from selenium import webdriver | ||
from selenium.webdriver.common.by import By | ||
from selenium.webdriver.common.keys import Keys | ||
from selenium.webdriver.remote.webelement import WebElement | ||
from selenium.webdriver.support.wait import WebDriverWait | ||
from selenium.webdriver.support import expected_conditions as EC | ||
from functools import partial | ||
from typing import Callable | ||
import clipboard | ||
|
||
# TODO: Implement post deletion | ||
# TODO: Read post information from JSON and validate its structure and data types | ||
# TODO: Refactor the module as a class | ||
# TODO: Implement file upload | ||
# - js in selenium 활용해서 drag & drop 구현 | ||
# - pyvirtualdisplay, pyautogui 활용 gui 조작 | ||
|
||
|
||
def wait_find_element(driver, by, value, timeout=10): | ||
return WebDriverWait(driver, timeout=timeout).until( | ||
# EC.presence_of_element_located((by, value)) | ||
EC.element_to_be_clickable((by, value)) | ||
) | ||
|
||
def clear_in_element(driver:webdriver.Chrome, by, value): | ||
wait_find_element(driver, by, value).click() | ||
webdriver.ActionChains(driver=driver).key_down(Keys.CONTROL).send_keys('A', Keys.BACKSPACE).perform() | ||
|
||
|
||
def copy_paste_in_element(driver:webdriver.Chrome, by, value, content): | ||
wait_find_element(driver, by, value).click() | ||
clipboard.copy(content) | ||
webdriver.ActionChains(driver=driver).key_down(Keys.CONTROL).send_keys('v').perform() | ||
|
||
|
||
def login_by_github( | ||
driver:webdriver.Chrome, | ||
username:str, | ||
password:str | ||
): | ||
XPATH:Callable[[str],WebElement] = partial(wait_find_element, driver, By.XPATH) | ||
|
||
login_page = 'https://github.com/login?client_id=7c3902d881910d52ae3e&return_to=%2Flogin%2Foauth%2Fauthorize%3Fclient_id%3D7c3902d881910d52ae3e%26integrateState%3D%26isIntegrate%3D0%26redirect_uri%3Dhttps%253A%252F%252Fv2.velog.io%252Fapi%252Fv2%252Fauth%252Fsocial%252Fcallback%252Fgithub%253Fnext%253D%26scope%3Duser%253Aemail' | ||
|
||
login_field = '//*[@id="login_field"]' | ||
password_field = '//*[@id="password"]' | ||
submit_button = '//*[@id="login"]/div[3]/form/div/input[13]' | ||
|
||
driver.get(login_page) | ||
|
||
XPATH(login_field).send_keys(username) | ||
XPATH(password_field).send_keys(password) | ||
XPATH(submit_button).click() | ||
|
||
# Reauthorization required 처리 | ||
authorize_button = '/html/body/div[1]/div[6]/main/div/div[2]/div[1]/div[2]/div[1]/form/div/button[2]' | ||
|
||
try: | ||
XPATH(authorize_button, 2).click() | ||
except: | ||
... | ||
|
||
def write_post( | ||
driver:webdriver.Chrome, | ||
title, | ||
content, | ||
tags=[], | ||
description=None, | ||
private=True, | ||
url_slug=None, | ||
series=None, | ||
thumbnail=None | ||
): | ||
XPATH:Callable[[str],WebElement] = partial(wait_find_element, driver, By.XPATH) | ||
copy_paste:Callable[[str, str]] = partial(copy_paste_in_element, driver, By.XPATH) | ||
clear:Callable[[str]] = partial(clear_in_element, driver, By.XPATH) | ||
|
||
title_field = '//*[@id="root"]/div[2]/div/div[1]/div/div[1]/div[1]/div/textarea' | ||
tag_field = '//*[@id="root"]/div[2]/div/div[1]/div/div[1]/div[1]/div/div[2]/div/input' | ||
content_field = '//*[@id="root"]/div[2]/div/div[1]/div/div[1]/div[3]/div/div[6]/div[1]/div/div' | ||
save_button = '//*[@id="root"]/div[2]/div/div[1]/div/div[2]/div/div/button[1]' | ||
publish_button = '//*[@id="root"]/div[2]/div/div[1]/div/div[2]/div/div/button[2]' | ||
public_button = '//*[@id="root"]/div[2]/div/div[1]/div/div[2]/div/div/button[1]' | ||
private_button = '//*[@id="root"]/div[2]/div[2]/div/div[3]/div[1]/section[1]/div/button[2]' | ||
url_slug_field = '//*[@id="root"]/div[2]/div[2]/div/div[3]/div[1]/section[2]/div/div/input' | ||
add_to_series_button = '//*[@id="root"]/div[2]/div[2]/div/div[3]/div[1]/section[3]/div/button' | ||
new_series_button = '//*[@id="root"]/div[2]/div[2]/div/div[3]/section/div/div[1]/div/form/div/div[2]/button[2]' | ||
series_ul = '//*[@id="root"]/div[2]/div[2]/div/div[3]/section/div/div[1]/ul' | ||
series_field = '//*[@id="root"]/div[2]/div[2]/div/div[3]/section/div/div[1]/div/form/input' | ||
select_series_button = '//*[@id="root"]/div[2]/div[2]/div/div[3]/section/div/div[2]/button[2]' | ||
submit_button = '//*[@id="root"]/div[2]/div[2]/div/div[3]/div[2]/button[2]' | ||
description_field = '//*[@id="root"]/div[2]/div[2]/div/div[1]/section/div/div[2]/textarea' | ||
|
||
driver.get('https://velog.io/write') | ||
|
||
copy_paste(title_field, title) | ||
|
||
for tag in tags: | ||
copy_paste(tag_field, tag) | ||
XPATH(tag_field).send_keys(Keys.RETURN) | ||
|
||
content += '\n\n#### Auto-generated by [Velog-Selenium-Action](https://github.com/oror-sine/Velog-Selenium-Action)' | ||
clipboard.copy(content) | ||
copy_paste(content_field, content) | ||
|
||
XPATH(publish_button).click() | ||
|
||
if description is not None: | ||
copy_paste(description_field, description) | ||
|
||
if private: | ||
XPATH(private_button).click() | ||
else: | ||
XPATH(public_button).click() | ||
|
||
if url_slug is not None: | ||
clear(url_slug_field) | ||
copy_paste(url_slug_field, url_slug) | ||
|
||
if series is not None: | ||
XPATH(add_to_series_button).click() | ||
XPATH(series_field).click() | ||
copy_paste(series_field, series) | ||
XPATH(new_series_button).click() | ||
for series_li in XPATH(series_ul).find_elements(By.TAG_NAME, 'li'): | ||
if series_li.text == series: | ||
series_li.click() | ||
break | ||
XPATH(select_series_button).click() | ||
|
||
if thumbnail is not None: | ||
raise NotImplementedError() | ||
|
||
XPATH(submit_button).click() | ||
|
||
title_element = '//*[@id="root"]/div[2]/div[3]/div/h1' | ||
print(XPATH(title_element).text) | ||
|
||
|
||
def edit_post(): | ||
NotImplementedError() | ||
|
||
|
||
|
||
|
||
if __name__ == '__main__': | ||
from datetime import datetime | ||
from get_driver import get_driver | ||
import sys | ||
|
||
_, username, password = (None, )*3 | ||
|
||
if len(sys.argv) == 1: | ||
from dotenv import dotenv_values | ||
username, password = dotenv_values('.env').values() | ||
|
||
elif len(sys.argv) == 3: | ||
_, username, password = sys.argv | ||
|
||
if username is None or password is None: | ||
raise Exception('missing login info') | ||
|
||
driver = None | ||
try: | ||
driver = get_driver(options=[]) | ||
except Exception: | ||
print(f'driver with display is not available: {Exception}') | ||
driver = get_driver() | ||
|
||
|
||
login_by_github(driver, username, password) | ||
|
||
|
||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | ||
str_date, str_time = timestamp.split() | ||
post = { | ||
'title': f'[TEST] {timestamp}', | ||
'content':'This post is created for testing Velog-Selenium-Action', | ||
'tags':['Auto-generated by Velog-Selenium-Action', str_date, str_time], | ||
'description':None, | ||
'private':True, | ||
'url_slug':'test', | ||
'series': 'Testing Velog-Selenium-Action', | ||
'thumbnail':None | ||
} | ||
|
||
write_post(driver, **post) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from selenium import webdriver | ||
import chromedriver_autoinstaller | ||
|
||
chromedriver_autoinstaller.install() | ||
|
||
_options = [ | ||
"--headless", | ||
"--disable-gpu", | ||
"--window-size=1920,1200", | ||
"--ignore-certificate-errors", | ||
"--disable-extensions", | ||
"--no-sandbox", | ||
"--disable-dev-shm-usage", | ||
'--remote-debugging-port=9222' | ||
] | ||
|
||
def get_driver(options = _options): | ||
chrome_options = webdriver.ChromeOptions() | ||
|
||
for option in options: | ||
chrome_options.add_argument(option) | ||
|
||
return webdriver.Chrome(options = chrome_options) | ||
|
||
if __name__ == "__main__": | ||
print(f'{"[Test `get_driver.py`]":-^100}\n') | ||
|
||
print(f'{"[Try Headless]":-^100}') | ||
status = '' | ||
try: | ||
driver_with_headless = get_driver() | ||
driver_with_headless.get('https://github.com/') | ||
title = driver_with_headless.title | ||
driver_with_headless.quit() | ||
|
||
status = f'{"Success": <10}: {title}' | ||
|
||
except Exception as err: | ||
status = f'{"Fail": <10}: {err}' | ||
|
||
print(status) | ||
print(f'{"":-^100}') | ||
|
||
|
||
print(f'{"[Try Display]":-^100}') | ||
status = '' | ||
try: | ||
driver_with_display = get_driver() | ||
driver_with_display.get('https://github.com/') | ||
title = driver_with_display.title | ||
driver_with_display.quit() | ||
|
||
status = f'{"Success": <10}: {title}' | ||
|
||
except Exception as err: | ||
status = f'{"Fail": <10}: {err}' | ||
|
||
print(status) | ||
print(f'{"":-^100}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
attrs==23.2.0 | ||
certifi==2024.2.2 | ||
cffi==1.16.0 | ||
chromedriver-autoinstaller==0.6.4 | ||
clipboard==0.0.4 | ||
h11==0.14.0 | ||
idna==3.6 | ||
outcome==1.3.0.post0 | ||
packaging==24.0 | ||
pycparser==2.22 | ||
pyperclip==1.8.2 | ||
PySocks==1.7.1 | ||
python-dotenv==1.0.1 | ||
selenium==4.19.0 | ||
sniffio==1.3.1 | ||
sortedcontainers==2.4.0 | ||
trio==0.25.0 | ||
trio-websocket==0.11.1 | ||
typing_extensions==4.10.0 | ||
urllib3==2.2.1 | ||
wsproto==1.2.0 |