너무 조잡한 코드라 비공개할까 한다..ㅋㅋㅋ

지금은 셀레니움과 requests를 적절히 사용해 속도를 대폭 높였고

정리가 좀 됐기 때문에..

 

몇달전 EBS 온라인클래스로 수업을 진행했을 당시엔

아침에 일어나 출석체크 하는것이 너무나도 귀찮았었다.

(지금도 고3을 제외한 나머지중 아직도 온라인 수업을 하는 학년이 있을것이다)

 

출석체크는 매일 올라오는 출결 게시글에 댓글을 달아야 했다.

수업은 점심때쯤부터 듣더라도 출석체크때문에 8시에 일어나 출석체크를 해야만 하는것

 

그래서 크롬 기반 자동 출석 프로그램을 파이썬으로 만들었다.

코드는 상당히 조잡하며 정리가 되어있지 않아 보기 힘들지만 내가 잊어버릴까봐 올린다.

메모장이라는 말

 

영상도 올리겠다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#-*- coding: utf-8 -*-
import os, sys, time, datetime, schedule, pyperclip, threading
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
 
 
# pyinstaller 컴파일 예제: pyinstaller --clean --icon=ebs.ico --onefile --add-binary "chromedriver.exe";"." EAC_v2.py
############################################## .ui 파일을 pyinstaller에 포함하기 위함
def resource_path(relative_path):   
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
 
############################################## 스레드
class ThreadClass_Main(QtCore.QThread): 
    def __init__(self, parent = None): 
        super(ThreadClass_Main,self).__init__(parent)
    def run(self):
        mainProgress()
 
threadclass_Main = ThreadClass_Main() # 스레드 전용 .start 쓰기위해
 
class ThreadClass_Scheduler(QtCore.QThread): 
    def __init__(self, parent = None): 
        super(ThreadClass_Scheduler,self).__init__(parent)
    def run(self):
        schedule.every().day.at(timeEdit).do(threadclass_Main.start)   # 스케줄러
        while True:
            schedule.run_pending()
            time.sleep(1)
 
threadclass_schedule = ThreadClass_Scheduler()
 
############################################## 메인
def mainProgress():
    loginURL="https://hoc5.ebssw.kr/sso/loginView.do?loginType=onlineClass&hmpgId=soraehigh302"
    if getattr(sys, 'frozen'False):  
        chromedriver_path = os.path.join(sys._MEIPASS, "chromedriver.exe")
        driver = webdriver.Chrome(chromedriver_path)
    else:
        driver = webdriver.Chrome()
 
    def copy_input(xpath, input):   # 클립보드.복사 + 액션체인.붙여넣기 설정
        pyperclip.copy(input)
        driver.find_element_by_xpath(xpath).click()
        ActionChains(driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
        time.sleep(1)
    global usrID, usrPW
    if not(usrID and usrPW):
        usrID = "아무것도 입력되지 않았을 시"
        usrPW = "이곳에 입력된 계정정보로 로그인"    # 프로그램 사용자가 1명뿐일때 매번 아이디와 비밀번호를 입력하기 귀찮을것
        
    driver.get(loginURL)
    copy_input('//*[@id="j_username"]', usrID)
    copy_input('//*[@id="j_password"]', usrPW)
    
    driver.find_element_by_xpath('//*[@class="img_type"]').click()  # 로그인버튼 클릭
 
    print()
    time.sleep(1)
    try:
        popup1 = driver.switch_to.alert  # 팝업창 예외처리
        if("새롭게 로그인 하시겠습니까?" in popup1.text):
            popup1.accept()
        elif("아이디를 입력해주세요" in popup1.text):
            print("아이디 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        elif("비밀번호를 입력해주세요" in popup1.text):
            print("비밀번호 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        elif("잘못된" in popup1.text):
            print("아이디 또는 비밀번호 재확인 필요\n#### 출석 실패 ####")
            driver.quit()
        else:
            print("알려지지 않은 오류: {}\n#### 출석 실패 ####".format(popup1.text))
    except:
        pass
    
    time.sleep(1)
    
    ################################ 출결 '게시판' 색출
    menuList_txt = []
    menuList_href = []
    menuList_temp = driver.find_elements_by_xpath('/html/body/div[2]/div[2]/div[2]/nav/ul/li[3]/div/a'# 해당 full xpath 하위경로들 리스트화
 
    for i in range(len(menuList_temp)):
        menuList_txt.append(menuList_temp[i].get_attribute('text'))     # text 메뉴 이름 리스트화
        menuList_href.append(menuList_temp[i].get_attribute('href'))    # href 메뉴 주소 리스트화
        
    for lst in range(0,len(menuList_txt),3):
        try:
            print("{:<10}{:<10}{}".format(menuList_txt[lst], menuList_txt[lst+1], menuList_txt[lst+2]))
        except IndexError:
            try:
                print("{:<10}{}".format(menuList_txt[lst], menuList_txt[lst+1]))
            except IndexError:
                print(menuList_txt[lst])
        
    print("\n{:<13}{:>2}개 ]".format("[ 출결방 메뉴 개수: "len(menuList_txt)))
 
    try:
        menu_for_srch = dateEdit
        menuNum = menuList_txt.index(menu_for_srch)
    except:
        splited = menu_for_srch.split()    #공백 없애기
        fixed = "".join(splited)
        menuNum = menuList_txt.index(fixed)
    driver.get(menuList_href[menuNum])
    time.sleep(1)
    
    ################################ 출결 '게시글' 색출
    pageLen = driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[2]/div[1]/a')
    print("{:<14}{:>2}개 ]".format("[ 총 페이지 개수: "len(pageLen)))
    pageLen_for_Range = len(pageLen) + 1
 
    postList_txt = []
    post_for_srch = "조례"
    for m in range(1, pageLen_for_Range):
        driver.find_element_by_xpath('//*[@id="swedu_listL"]/div[2]/div[1]/a[{}]'.format(m)).click()
        time.sleep(2)
        print("\n\n==== 페이지 {} 진입 ====".format(m))
 
        postList_txt.clear()
        postList_temp = driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr/td[1]/a/span')
        for j in range(len(postList_temp)):
            postList_txt.append(driver.find_elements_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr/td[1]/a/span')[j].text)
 
        for postlst in range(0,len(postList_txt),3):
            try:
                print("{:<17}{:<17}{}".format(postList_txt[postlst], postList_txt[postlst+1], postList_txt[postlst+2]))
            except IndexError:
                try:
                    print("{:<17}{}".format(postList_txt[postlst], postList_txt[postlst+1]))
                except IndexError:
                    print(postList_txt[postlst])
 
        print("\n[ 확인 단계 ]")
        for k in range(0len(postList_txt)):
            tempvalue = postList_txt[k].count(post_for_srch)
            print("{} --- 확인중".format(postList_txt[k]))
            if tempvalue == 1:
                print("\n[ 해당 게시글이 출석 게시글로 확인됨 ]")
                postNum = k+1
                break
    print("[ 색출된 게시글 인덱스: {} ]".format(postNum))
 
    driver.find_element_by_xpath('//*[@id="swedu_listL"]/div[1]/table/tbody/tr[{}]/td[1]/a/span'.format(postNum)).click()
    time.sleep(2)
 
    print("[ 댓글 입력 중 ]")
    temp = driver.find_element_by_xpath('//*[@name="cmmntsCn"]')    # 댓글창 클릭
    temp.click()
    time.sleep(2)
    temp.send_keys(toWrite)
 
    driver.find_element_by_xpath('//*[@class="submit"]').click()    # 댓글등록버튼 클릭
    try:
        popup2 = driver.switch_to.alert  # 팝업창 확인
        popup2.accept()
    except:
        pass
 
    print ("\n#### 출석 완료 ####\n완료시각: [{}]".format(datetime.datetime.now()))
 
############################################## GUI 생성
form = resource_path('UI_v3.ui')
try:
    form_class = uic.loadUiType("UI_v3.ui")[0]
except:
    form_class = uic.loadUiType(form)[0]
 
class MyWindow(QDialog, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setFixedSize(342,236# 화면크기조정 제한
 
        self.pushButton_timeset.clicked.connect(self.btnEvent_timeset)   # 버튼 이벤트
        self.pushButton_now.clicked.connect(self.btnEvent_now)   # 버튼 이벤트
    def btnEvent_timeset(self):
        global usrID, usrPW, dateEdit, timeEdit, toWrite  # 계정정보와 댓글내용과 주소 저장
        usrID = self.lineEdit_id.text()
        usrPW = self.lineEdit_pw.text()
        toWrite = self.textEdit_comment.toPlainText()
        tempdateEdit = self.dateEdit.date() # dateEdit 에서 QDate형태 객체 반환
        dateEdit = tempdateEdit.toString("M월 d일"# QDate형태 객체 문자열로 출력
        temptimeEdit = self.timeEdit_set.time() # timeEdit 에서 QTime형태 객체 반환
        timeEdit = temptimeEdit.toString("hh:mm:ss")    # QTime형태 객체 문자열로 출력
        print("ID: {}\nPW: {}\nComment: {}\nDate to attend: {}\n\n#### Run at: {} ####".format(usrID, usrPW, toWrite, dateEdit, timeEdit))
        threadclass_schedule.start()    # 스레드 시작
        
    def btnEvent_now(self):
        global usrID, usrPW, dateEdit, toWrite  # 계정정보와 댓글내용과 주소 저장
        usrID = self.lineEdit_id.text()
        usrPW = self.lineEdit_pw.text()
        toWrite = self.textEdit_comment.toPlainText()
        tempdateEdit = self.dateEdit.date() # dateEdit 에서 QDate형태 객체 반환
        dateEdit = tempdateEdit.toString("M월 d일"# QDate형태 객체 문자열로 출력
        print("ID: {}\nPW: {}\nComment: {}\nDate to attend: {}\n\n#### Run Immediately ####".format(usrID, usrPW, toWrite, dateEdit))
        threadclass_Main.start()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()
 
cs

 

 

 

.py > .exe 컴파일 & .ui 파일 포함시키는 방법

명령프롬프트(cmd)에서 cd 명령어를 통해 해당 .py파일이 있는 위치로 이동한 후

코드 상단 예제와 같이 pyinstaller를 통해 컴파일을 진행한다.

 

pyinstaller --clean --icon=ebs.ico --onefile --add-binary "chromedriver.exe";"." EAC_v2.py

 

컴파일이 완료되면 .py파일이 있는 디렉토리에 dist 폴더가 생기고 그 안에 .exe파일이 생긴다. 아직 건들지 말자.

이때 중요한점은 .py파일과 같은 이름의 .spec 파일이 생성되는데, 메모장으로 편집을 해 주어야 한다.

datas=[('UI_v3.ui', '.')] 로 바꿔준다. 위 코드에서 불러오는 UI파일 이름이 UI_v3.ui 이다.

마지막으로 cmd에 방금 편집한 .spec 파일도 컴파일해준다.

 

pyinstaller EAC_v2.spec

 

그럼 ui파일까지 포함된것이다. dist폴더의 실행파일은 이제 다른 윈도우 운영체제 컴퓨터에서도 작동한다.

 

참고한 사이트 목록

기본적인 기능 키워드: 셀레니움, 셀레니움 alert, 스케줄러, paperclip, 셀레니움 로그인 등등

 

threading 라이브러리를 통한 스레드 구현 (가장 유용했던 사이트는 못찾았다)

>https://niceman.tistory.com/138

 

pyqt를 이용한 UI

>http://pythonstudy.xyz/python/article/108-PyQt-QtDesigner

>https://nanite.tistory.com/53

>https://wikidocs.net/35478

 

pyinstaller를 이용한 컴파일과 ui 포함방법

>https://wikidocs.net/21952

>https://m.blog.naver.com/PostView.nhn?blogId=yhs11145&logNo=221450157087&proxyReferer=https:%2F%2Fwww.google.com%2F

>https://pagedown.n-e.kr/11

>https://stackoverflow.com/questions/59719083/pyinstaller-ui-files?noredirect=1&lq=1

'hobby > 코딩' 카테고리의 다른 글

비주얼 스튜디오 2017 프로페셔널 웹인스톨러  (0) 2020.05.11
아두이노 다크 테마 적용법  (0) 2019.12.31

OpenCV 강의에서 작성일 기준 최신버전인 vs2019 대신 vs2017 바탕으로 설명되어있어서

혹시모를 호환성 문제를 방지하고자 버전을 맞춰주는게 맞다고 판단해 설치파일을 찾아 공유한다.

 

아래는 vs2017 웹 인스톨러 파일이다.

vs2017_Community.exe
1.20MB
vs2017_Professional.exe
1.20MB

설치 후 로그인을 통해 제품인증을 하여 사용하는것을 적극 권장한다.

'hobby > 코딩' 카테고리의 다른 글

파이썬 온라인클래스 자동 출석 프로그램  (1) 2020.08.10
아두이노 다크 테마 적용법  (0) 2019.12.31

아두이노에도 다크 테마가 있다.

물론 유저 커스터마이징

 

블루라이트 차단으로 눈의 피로 줄이고

멋있어보이는 시각효과는 덤

 

적용법은 매우 간단하다.

https://github.com/jeffThompson/DarkArduinoTheme

 

jeffThompson/DarkArduinoTheme

A dark theme for the Arduino IDE. Contribute to jeffThompson/DarkArduinoTheme development by creating an account on GitHub.

github.com

위 사이트(깃헙)에 들어가서 파일을 받고 압축을 해제한 후 나오는 theme 폴더를

Arduino IDE가 설치된 폴더(보통 "C:\Program Files (x86)\Arduino") 속 lib 폴더 안에 덮어씌운다.

물론 기존 폴더는 백업을 권장한다.

 

기존 theme 폴더 이름을 바꿔놓고 다크테마 theme을 넣어주면 아래와 같은 현상이 일어나지 않는다.

이러면 다크 테마 적용 끝인데, 필자의 경우 괄호를 강조하는 효과 색상만 다크 테마 적용이 안되길래 찾아보니깐

theme 폴더 속 syntax 폴더 안에 있는 default.xml 파일을 지워버리던 이름만 바꾸던 한 후에

같이 있던 dark.xml 파일을 default.xml로 이름을 바꿔주면 해결된다고 한다.

 

아래는 다크 테마가 적용된 모습.

눈을 보호하도록 하자

 

hobby/그림

올빼미

2019. 8. 13. 22:38


지금보면 한없이 부족한 그림

'hobby > 그림' 카테고리의 다른 글

  (0) 2019.08.12

hobby/그림

2019. 8. 12. 08:04


손이다

공간지각능력은 인물화 관련된것들 그리는데에 별 도움이 안되는듯 하다

'hobby > 그림' 카테고리의 다른 글

올빼미  (0) 2019.08.13

방법 설명하기에 앞서 먼저 이 강좌를 보고 루팅하신 후 발생하는 불이익에 대해 글쓴이는 책임지지 않습니다!

가급적 금융어플이나 삼성PASS를 사용할 일이 없는 공기계에 루팅하시길 바랍니다


[참고]

이 방법을 통해 SuperSu 를 기기에 설치하는 것 보다

Magisk 를 설치하시는게 녹스를 안건드릴 확률이 높고 보다 깔끔합니다.

지금으로썬 비추합니다.


[주의사항]


삼성의 스마트폰에는 녹스(Knox) 라는 강력한 보안 프로그램이 있습니다

( 상세정보: https://www.samsungknox.com/ko/knox-platform )

여기서 설명하는 루팅 방법은 녹스를 우회하지 않습니다.

따라서 루팅을 한다는건 녹스의 보안을 뚫는다는 것 입니다. 하지만 문제는 여기서 발생합니다.


한번 녹스의 보안이 뚫리게 되면 녹스는 warranty void 값을 영구적으로 바꾸고, 이 값을 되돌릴 순 없게 됩니다.

즉 녹스와 관련된 Samsung PASS, Samsung Health 등의 어플들을 영구적으로 사용하실수 없다는겁니다. (as센터에서도 불가능)

녹스와는 관련이 없는 금융 어플들은 루팅 후에 사용이 불가능하긴 하나, 언루팅하면 다시 사용가능하지만 녹스는 언루팅이 소용없습니다.


자신의 녹스 warranty void값을 확인하는 방법:

먼저 기기의 전원을 끈 후, 볼륨 하+홈 버튼+전원 을 꾹 눌러 다운로드 모드로 진입하면 확인할 수 있습니다. 

▲ 위 사진과 조금은 차이가 있을 수 있으나 결과적으로 warranty void가 0x0 또는 0 이라면 순정, 0x1 또는 1 이라면 루팅이 기록된것입니다.


*몇년전까진 triangle away 라는 어플로 flash counter 값을 지울수 있었던거 같은데 지금은 막혔습니다.


*녹스의 작동원리에는 크게 2가지 추측이 있다고 하는데

 가장 유력한건 녹스가 뚫리는 동시에 기판 회로의 e-fuse가 끊어져 메인보드 교체 말고는 다시 되돌릴 수 없게끔 하는것이고

 다른 하나는 수정이 불가능한 파티션이 따로 존재하여 보안을 기록한다는 것입니다.







[루팅 방법 - 사용자의 데이터가 모두 초기화되므로 삼성 스마트스위치 등으로 백업을 합시다]


이 글에서 알려드리는 루팅법은 2019.02.28 기준 안드로이드 버전 8.x 오레오(OREO)에 맞는 루트 펌웨어를 찾지 못했기 때문에

안드로이드 버전 7.1.1 누가(NOUGAT)로 다운그레이드 하여 루팅하기 때문에 이점 숙지해주시기 바랍니다.



1) 아래 파일 다운로드


[sm-g611s 7.1.1 펌웨어]  <-- 2GB쯤 되니까 이게 제일 오래걸릴겁니다


[리커버리 파일 다운로드]  <-- 이것도 같이 받아줍니다


  Odin3_v3.13.1.zip


  no-verity-opt-encrypt-6.0.zip


  Nougat_SuperSU_J7_(P)_7.0.zip


다운로드가 완료되면 no-verity-opt 파일과 Nougat_SuperSU 파일을 스마트폰의 외장 SD카드에 넣어놓습니다

USB에 저장해놓고 OTG 케이블로 연결하는 방법도 있습니다.



2) 오딘 사용에 앞서

▲ 먼저 스마트폰 설정 > 휴대전화 정보 > 소프트웨어 정보 > 빌드번호 를 호다다다닥 눌러줍니다.

그러면 개발자 모드가 활성화되었다고 뜹니다



▲ 이제 설정 > 개발자 옵션 에서 OEM 잠금해제USB 디버깅을 위 사진처럼 활성화합니다.



3) 누가(Nougat) 7.1.1로 다운그레이드 하기

스마트폰 전원을 끈 후에 볼륨 하+홈 버튼+전원 을 꾹 눌러 다운로드 모드로 진입한 후

PC에서 좀전에 다운받은 오딘을 실행한 후 스마트폰과 PC를 연결합니다


그다음 다운받은 펌웨어 파일을 압축풀고 나온 파일들을 BL, AP, CP, CSC에 차례대로 집어넣습니다 (HOME_CSC는 무시합니다)

여기서 주의할점은 왼쪽 Option에 들어가서 Auto Reboot F.Reset TIme 체크 해제 해주셔야 합니다


이제 Start를 누르고 아래와 같이 PASS가 뜰 때까지 기다립니다

완료가 된 모습. 이제 볼륨 하+홈 버튼+전원을 눌러서 시스템으로 부팅할 수 있다



4) 커스텀 펌웨어 TWRP 설치

*다운그레이드 후 시스템 부팅해서 USB 디버깅을 또다시 허용해야 하는지는 잘 모르겠지만

글쓴이의 경우엔 다운그레이드 후 스마트폰 기본 설정을 마치고 다시 twrp를 설치함


다시 볼륨 하+홈 버튼+전원을 눌러서 다운로드 모드로 진입 후에 오딘과 연결하고

AP에 다운받은 recovery.tar 파일을 넣고 Start를 눌러줍니다. 이또한 옵션에서 체크 해제를 잊지맙시다.

TWRP 리커버리를 제대로 넣은 모습


완료되었다면 다운로드모드를 나가고, 재부팅 때 볼륨 '상'+홈 버튼+전원 버튼을 꾹 눌러 리커버리모드로 진입합니다.

위와 같이 TWRP 리커버리 모드로 진입한걸 확인할 수 있습니다


이제 TWRP 인터페이스에서 Wipe를 눌러 충돌방지를 위해 factory reset을 합니다.

wipe에 들어가면 맨 아래에 있습니다.


그다음 다시 Install에 들어가서 앞서 따로 저장해놓은 Nougat_SuperSU 파일을 설치해줍니다.


설치가 완료되면 리부트를 누르고 TWRP 설치하지 않음 버튼을 누르면 끝나게 됩니다.