LFU : Last Fotress Underground – 라스트포트리스 홈페이지 출석 체크 로그인 하기 귀찮아서 제작
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>출석체크 매크로</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<h1>출석체크 매크로</h1>
<div class="form-group">
<label for="account_id">계정 ID:</label>
<input type="text" id="account_id" placeholder="계정 ID 입력">
</div>
<div class="form-group">
<label for="saved_ids">저장된 ID:</label>
<select id="saved_ids">
<option value="">저장된 ID 선택</option>
</select>
</div>
<div class="button-group">
<button id="save_id_btn">ID 저장</button>
<button id="check_in_btn">출석체크</button>
</div>
<pre id="output"></pre>
</div>
<script>
$(document).ready(function() {
function loadSavedIds() {
var savedIds = JSON.parse(localStorage.getItem('savedIds') || '[]');
var select = $('#saved_ids');
select.empty();
select.append('<option value="">저장된 ID 선택</option>');
savedIds.forEach(function(id) {
select.append('<option value="' + id + '">' + id + '</option>');
});
}
loadSavedIds();
$('#save_id_btn').click(function() {
var accountId = $('#account_id').val();
if (accountId) {
var savedIds = JSON.parse(localStorage.getItem('savedIds') || '[]');
if (!savedIds.includes(accountId)) {
savedIds.push(accountId);
localStorage.setItem('savedIds', JSON.stringify(savedIds));
loadSavedIds();
}
} else {
alert("계정 ID를 입력해주세요.");
}
});
$('#saved_ids').change(function() {
var selectedId = $(this).val();
if (selectedId) {
$('#account_id').val(selectedId);
}
});
$('#check_in_btn').click(function() {
var accountId = $('#account_id').val();
if (accountId) {
$.ajax({
url: '/run_automation',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ account_id: accountId }),
success: function(response) {
$('#output').text(response.output || '응답 오류: 데이터가 없습니다.');
},
error: function(error) {
$('#output').text('오류 발생: ' + error.responseText);
}
});
} else {
alert("계정 ID를 입력해주세요.");
}
});
// 실시간 로그 스트리밍
const eventSource = new EventSource('/logs');
eventSource.onmessage = function (e) {
const log = document.getElementById('output');
log.textContent += e.data + "\n";
log.scrollTop = log.scrollHeight;
};
});
</script>
</body>
</html>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 500px;
}
h1 {
margin-bottom: 20px;
font-size: 24px;
text-align: center;
}
.form-group {
margin-bottom: 15px;
display: flex;
align-items: center;
}
label {
flex: 1;
margin-right: 10px;
font-weight: bold;
}
input[type="text"], select {
flex: 2;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
.button-group {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.button-group button {
width: 48%;
padding: 10px;
background: #007BFF;
border: none;
border-radius: 4px;
color: white;
font-size: 16px;
cursor: pointer;
}
.button-group button:hover {
background: #0056b3;
}
pre {
margin-top: 20px;
background: #f4f4f4;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
}
@media (max-width: 600px) {
h1 {
font-size: 20px;
}
input[type="text"], select, button {
font-size: 14px;
padding: 8px;
}
.button-group {
flex-direction: column;
gap: 10px;
}
.button-group button {
width: 100%;
}
}
from flask import Flask, request, jsonify, render_template, Response
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import os
import logging
from io import StringIO
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl
app = Flask(__name__)
# Logging 설정
log_stream = StringIO()
logging.basicConfig(stream=log_stream, level=logging.INFO, format='%(asctime)s - %(message)s')
@app.route('/')
def index():
return render_template('test3.html')
def generate_log():
while True:
log_content = log_stream.getvalue()
if log_content:
yield f"data: {log_content}\n\n"
log_stream.truncate(0)
log_stream.seek(0)
@app.route('/logs')
def stream_logs():
return Response(generate_log(), mimetype='text/event-stream')
@app.route('/run_automation', methods=['POST'])
def run_automation():
data = request.get_json()
account_id = data['account_id']
driver_path = data.get('driver_path', 'C:/dev/seleniumdrivers/chromedriver.exe')
service = Service(driver_path)
options = webdriver.ChromeOptions()
options.add_argument('--disable-extensions')
options.add_argument('--disable-infobars')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--window-size=1920x1080')
options.add_argument('--headless')
options.add_argument('--blink-settings=imagesEnabled=false')
try:
driver = webdriver.Chrome(service=service, options=options)
logging.info("웹사이트 열기 완료")
driver.get("https://lf-vip.store.koppay.net/integral/ko")
logging.info("로그인 페이지 로드 완료")
login_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".no_login_btn"))
)
login_button.click()
logging.info("로그인 버튼 클릭 완료")
account_id_field = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".site-input__inner"))
)
account_id_field.clear()
account_id_field.send_keys(account_id)
logging.info(f"계정 ID 입력 완료: {account_id}")
green_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".check_btn._flex"))
)
green_button.click()
logging.info("초록색 버튼 클릭 완료")
confirm_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".cfm_btn"))
)
confirm_button.click()
logging.info("확인 버튼 클릭 완료")
user_info = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".user_info"))
)
logging.info("로그인 성공")
try:
close_icon = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".close_icon"))
)
close_icon.click()
logging.info("팝업창 닫기 완료")
except Exception as e:
logging.error(f"팝업창 닫기 실패: {str(e)}")
try:
sign_in_buttons = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".day_btn"))
)
logging.info(f"수령 버튼 수: {len(sign_in_buttons)}")
button_clicked = False
for button in sign_in_buttons:
button_text = button.text
if "수령" in button_text:
try:
button.click()
logging.info("수령 버튼 클릭 완료")
button_clicked = True
break
except Exception as click_exception:
logging.error(f"수령 버튼 클릭 오류: {str(click_exception)}")
if not button_clicked:
logging.info("수령 버튼 누를거 없음")
except Exception as e:
logging.error(f"수령 버튼을 찾는 중 오류 발생: {str(e)}")
driver.quit()
except Exception as e:
logging.error(f"오류 발생: {str(e)}")
driver.quit()
return jsonify({"output": "작업 완료"})
if __name__ == '__main__':
# Flask 앱을 백그라운드에서 실행
from threading import Thread
server_thread = Thread(target=lambda: app.run(debug=False, host='127.0.0.1', port=5000))
server_thread.start()
# PyQt5 애플리케이션
app = QApplication(sys.argv)
browser = QWebEngineView()
browser.setWindowTitle("LFU Automation")
browser.resize(1280, 720)
browser.setUrl(QUrl("https://127.0.0.1:5000"))
browser.show()
sys.exit(app.exec_())
chromedriver 다운 페이지
chromdriver를 설치해서 경로 지정해줘야 함
실행 방식
- 웹페이지 뜨지 않고 헤드리스 방식
- 실시간 로그 확인 방식
- 오류 발생하는 이유
- 많은 계정 로그인 시켜서 봇 방지 팝업창 때문에 오류
- 수령 버튼이 활성화 되어 있지 않는데 7개 수령 버튼을 눌렀을시 오류 로그 출력
- 봇 방지 우회는 넣지 않음
- 출석체크 상자 수령하는 방식은 ‘수령’ 버튼이 활성화 되어있는것을 누르는 방식이 아닌 7개 그냥 다 누르는 방식(무식한 방식)
OWASP(오픈 웹 애플리케이션 보안 프로젝트)는 웹 애플리케이션 보안에 대한 정보를 제공하는 비영리 단체입니다. OWASP는 주기적으로…
XSS는 공격자가 웹 페이지에 악성 스크립트를 삽입하여 다른 사용자의 정보를 탈취하는 기법입니다. 예제 다음은 XSS…