RPA(로봇 프로세스 자동화)는 인간 작업자와 마찬가지로 다양한 애플리케이션에서 실행되는 소프트웨어 또는 하드웨어 시스템을 통해 작업을 자동화합니다. 소프트웨어 또는 봇은 양식 수신, 확인 메시지 보내기, 양식 무결성 확인, 폴더에 양식 정리, 양식 이름, 제출 날짜 대기로 스프레드시트 업데이트 등 여러 단계와 애플리케이션을 통해 워크플로를 학습할 수 있습니다. RPA 소프트웨어는 직원들이 반복적이고 간단한 작업을 완료해야 하는 부담을 덜어주기 위해 설계되었습니다.
해당 클라이언트가 로컬 컴퓨터에 설치되어 있고 rpa-client
와 rpa-server
모두 시작되었는지 확인하십시오.
현재 시스템은 다음 클라이언트를 지원합니다.
앱 ID | 이름 |
---|---|
위챗 | 위챗 |
웨콤 | 엔터프라이즈 위챗 |
텐센트 QQ | |
팀 | 팀 |
딩토크 | 딩톡 |
종달새 | 페이슈 |
작업은 지정된 사용자에 의해 실행되어야 하므로 작업을 실행하기 전에 해당 사용자가 존재하는지 확인해야 합니다.
http://<host>:<port>/users
POST
JSON
Body
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
사용자 | 사용자[] | 필수의 | 사용자 개체의 배열입니다. |
└아이디 | 끈 | 선택 과목 | 사용자 ID. 비어 있으면 서버가 자동으로 ID를 생성합니다. |
└앱ID | 끈 | 필수의 | 연결된 AppID입니다. |
└계정 | 끈 | 필수의 | 클라이언트를 일치시키는 데 사용되는 사용자 계정입니다. |
└ 닉네임 | 끈 | 선택 과목 | 표시에 사용되는 사용자 닉네임입니다. |
└ 본명 | 끈 | 선택 과목 | 표시에는 사용자의 실명이 사용됩니다. |
└회사 | 끈 | 선택 과목 | 표시에는 사용자가 속한 회사의 이름이 사용됩니다. |
참고로
企业微信
클라이언트는 현재 로그인한 사람의account
직접 얻을 수 없습니다. 현재는${realname}_${company}
조합을 통해 클라이언트를 매칭하고 있습니다.
인터페이스 예:
curl -X POST --location " http://localhost:8080/users "
-H " Content-Type: application/json "
-d " {
" users " : [
{
" id " : " uid " ,
" appId " : " wechat " ,
" account " : " account " ,
" nickname " : " nickname "
}
]
} "
insert into user (id, app_id, account, nickname, realname, company, status, created_time, updated_time)
values ( ' uid ' , ' wechat ' , ' account ' , ' nickname ' , ' realname ' , ' company ' , 1 , now(), null );
http://<host>:<port>/tasks
POST
JSON
Body
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
작업 | 일[] | 필수의 | 작업 개체의 배열입니다. |
└아이디 | 끈 | 선택 과목 | 태스크 ID. 비어 있으면 서버가 자동으로 ID를 생성합니다. |
└ 사용자ID | 끈 | 필수의 | 연결된 사용자 ID입니다. |
└ 종류 | 끈 | 필수의 | 작업 유형은 작업 유형 사전표를 참조하세요. |
└ 우선순위 | 정수 | 선택 과목 | 작업 우선순위는 값이 작을수록 우선순위가 높습니다. 비어 있으면 기본으로 구성된 우선순위가 사용됩니다. |
└데이터 | 끈 | 선택 과목 | JSON 문자열 형식의 작업 데이터입니다. |
└ 일정시간 | 날짜/시간 | 선택 과목 | 작업 실행 시간(예: 2022-01-01 10:00:00 ) 비어 있으면 즉시 실행됩니다. |
예:
curl -X PATCH --location " http://localhost:8080/tasks "
-H " Content-Type: application/json "
-d " {
" tasks " : [
{
" id " : " tid " ,
" userId " : " uid " ,
" type " : " login " ,
" priority " : " 0 " ,
" data " : "" ,
" scheduleTime " : " 2022-01-01 10:00:00 " ,
}
]
} "
insert into task (id, user_id, app_id, type, priority, data, status, created_time, updated_time, schedule_time)
values ( ' tid ' , ' uid ' , ' wechat ' , ' login ' , 0 , null , 0 , now(), null , ' 2022-01-01 10:00:00 ' );
서버는 개발자가 로컬에서 간단한 작업을 테스트하는 데 사용할 수 있는 런타임 테스트 페이지를 제공합니다. 브라우저를 열고 http://<host>:<port>/index.html
방문한 후 테스트할 클라이언트를 선택하세요.
클라이언트에 로그인하는 데 매개변수가 필요하지 않습니다.
클라이언트에서 로그아웃하는 데 매개변수가 필요하지 않습니다.
매개변수 형식:
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
목표 | 끈 | 필수의 | 개체를 보냅니다. |
메시지 | 메시지[] | 필수의 | 메시지 객체의 배열. |
└ 종류 | 끈 | 필수의 | 메시지 유형. |
└ 내용 | 끈 | 필수의 | 메시지 내용, 텍스트 내용 또는 파일 주소. |
메시지 유형:
암호 | 이름 | 설명 |
---|---|---|
텍스트 | 텍스트 | 텍스트 |
영상 | 영상 | 그림 |
동영상 | 동영상 | 동영상 |
파일 | 파일 | 문서 |
매개변수 예:
{
"target" : " friend " ,
"messages" : [
{
"type" : " text " ,
"content" : " message "
},
{
"type" : " image " ,
"content" : " https://rpa.leego.io/image.png "
},
{
"type" : " video " ,
"content" : " https://rpa.leego.io/video.mp4 "
},
{
"type" : " file " ,
"content" : " https://rpa.leego.io/file.zip "
}
]
}
매개변수 형식:
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
목표 | 끈 | 필수의 | 그룹 이름. |
메시지 | 메시지[] | 필수의 | 메시지 객체의 배열. |
└ 종류 | 끈 | 필수의 | 메시지 유형. |
└ 내용 | 끈 | 필수의 | 메시지 내용, 텍스트 내용 또는 파일 주소. |
메시지 유형:
암호 | 이름 | 설명 |
---|---|---|
텍스트 | 텍스트 | 텍스트 |
영상 | 영상 | 그림 |
동영상 | 동영상 | 동영상 |
파일 | 파일 | 문서 |
언급하다 | 언급하다 | 상기시키다 |
매개변수 예:
{
"target" : " group " ,
"messages" : [
{
"type" : " text " ,
"content" : " message "
},
{
"type" : " image " ,
"content" : " https://rpa.leego.io/image.png "
},
{
"type" : " video " ,
"content" : " https://rpa.leego.io/video.mp4 "
},
{
"type" : " file " ,
"content" : " https://rpa.leego.io/file.zip "
},
{
"type" : " mention " ,
"content" : " member "
}
]
}
클라이언트에 로그인하는 데 매개변수가 필요하지 않습니다.
클라이언트에서 로그아웃하는 데 매개변수가 필요하지 않습니다.
매개변수 형식:
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
목표 | 끈 | 필수의 | 개체를 보냅니다. |
메시지 | 메시지[] | 필수의 | 메시지 객체의 배열. |
└ 종류 | 끈 | 필수의 | 메시지 유형. |
└ 내용 | 끈 | 필수의 | 메시지 내용, 텍스트 내용 또는 파일 주소. |
메시지 유형:
암호 | 이름 | 설명 |
---|---|---|
텍스트 | 텍스트 | 텍스트 |
영상 | 영상 | 그림 |
동영상 | 동영상 | 동영상 |
파일 | 파일 | 문서 |
매개변수 예:
{
"target" : " friend " ,
"messages" : [
{
"type" : " text " ,
"content" : " message "
},
{
"type" : " image " ,
"content" : " https://rpa.leego.io/image.png "
},
{
"type" : " video " ,
"content" : " https://rpa.leego.io/video.mp4 "
},
{
"type" : " file " ,
"content" : " https://rpa.leego.io/file.zip "
}
]
}
매개변수 형식:
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
목표 | 끈 | 필수의 | 그룹 이름. |
메시지 | 메시지[] | 필수의 | 메시지 객체의 배열. |
└ 종류 | 끈 | 필수의 | 메시지 유형. |
└ 내용 | 끈 | 필수의 | 메시지 내용, 텍스트 내용 또는 파일 주소. |
메시지 유형:
암호 | 이름 | 설명 |
---|---|---|
텍스트 | 텍스트 | 텍스트 |
영상 | 영상 | 그림 |
동영상 | 동영상 | 동영상 |
파일 | 파일 | 문서 |
언급하다 | 언급하다 | 상기시키다 |
매개변수 예:
{
"target" : " group " ,
"messages" : [
{
"type" : " text " ,
"content" : " message "
},
{
"type" : " image " ,
"content" : " https://rpa.leego.io/image.png "
},
{
"type" : " video " ,
"content" : " https://rpa.leego.io/video.mp4 "
},
{
"type" : " file " ,
"content" : " https://rpa.leego.io/file.zip "
},
{
"type" : " mention " ,
"content" : " member "
}
]
}
매개변수 형식:
재산 | 유형 | 필수의 | 설명 |
---|---|---|---|
목표 | 끈 | 필수의 | 그룹 이름. |
콘택트 렌즈 | 연락하다[] | 필수의 | 연락처 개체의 배열입니다. |
└ 대상 | 끈 | 필수의 | 이용자의 휴대전화번호 또는 이메일 주소. |
└ 이유 | 끈 | 선택 과목 | 연락처 메모를 추가합니다. |
매개변수 예:
{
"contacts" : [
{ "target" : " phone " },
{ "target" : " email " , "reason" : " reason " }
]
}
표시하기에는 너무 고급입니다.
클라이언트가 여러 번 시작되는 것을 방지하기 위해 개발자는 일반적으로 뮤텍스 개체를 만듭니다. 뮤텍스는 다중 스레드 프로그래밍에서 공유 리소스가 여러 스레드나 프로세스에서 동시에 액세스되지 않도록 보호하기 위해 사용되는 메커니즘입니다.
HANDLE CreateMutexA (
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
[in] BOOL bInitialOwner,
[in, optional] LPCSTR lpName
);
[in, optional] lpMutexAttributes
SECURITY_ATTRIBUTES 구조에 대한 포인터입니다. 이 매개변수가 NULL
이면 핸들은 하위 프로세스에서 상속될 수 없습니다.
구조의 lpSecurityDescriptor
멤버는 새 뮤텍스에 대한 보안 설명자를 지정합니다. lpMutexAttributes
가 NULL
이면 뮤텍스는 기본 보안 설명자를 가져옵니다. 뮤텍스의 기본 보안 설명자에 있는 ACL
작성자의 기본 또는 가장 토큰에서 나옵니다.
[in] bInitialOwner
이 값이 TRUE
이고 호출자가 뮤텍스를 생성하는 경우 호출 스레드는 뮤텍스 개체의 초기 소유권을 갖습니다. 그렇지 않으면 호출 스레드가 뮤텍스 잠금의 소유권을 갖지 않습니다. 호출자가 뮤텍스를 생성했는지 확인하려면 반환 값 섹션을 참조하세요.
[in, optional] lpName
뮤텍스 개체의 이름입니다. 이름은 MAX_PATH
문자로 제한됩니다. 이름 비교에서는 대소문자를 구분합니다.
lpName
기존 명명된 뮤텍스의 이름과 일치하는 경우 이 함수는 MUTEX_ALL_ACCESS
액세스를 요청합니다. 이 경우 bInitialOwner
매개변수는 생성 프로세스에서 이미 설정되었으므로 무시됩니다. lpMutexAttributes
매개 변수가 NULL이 아닌 경우 핸들을 상속할 수 있는지 여부를 결정하지만 해당 보안 설명자 멤버는 무시됩니다.
lpName
이 NULL
이면 이름 없이 뮤텍스가 생성됩니다.
lpName
기존 이벤트, 세마포어, 대기 가능 타이머, 작업 또는 파일 매핑 개체의 이름과 일치하는 경우 함수는 실패하고 GetLastError 함수는 ERROR_INVALID_HANDLE
반환합니다. 이는 이러한 개체가 동일한 네임스페이스를 공유하기 때문입니다.
이름에는 전역 또는 세션 네임스페이스에 개체를 명시적으로 생성하기 위한 "전역" 또는 "로컬" 접두사가 있을 수 있습니다. 이름의 나머지 부분에는 백슬래시 문자()를 제외한 모든 문자가 포함될 수 있습니다. 자세한 내용은 커널 개체 네임스페이스를 참조하세요. 빠른 사용자 전환을 위해 터미널 서비스 세션을 사용하십시오. 커널 개체 이름은 응용 프로그램이 여러 사용자를 지원할 수 있도록 터미널 서비스에 대해 설명된 지침을 따라야 합니다.
개체는 개인 네임스페이스에서 생성될 수 있습니다. 자세한 내용은 개체 네임스페이스를 참조하세요.
함수가 성공하면 반환값은 새로 생성된 뮤텍스 객체의 핸들(Handle)입니다.
함수가 실패하면 반환 값은 NULL
입니다. 확장된 오류 정보를 얻으려면 GetLastError 함수를 호출하십시오.
뮤텍스가 명명된 뮤텍스이고 이 함수가 호출되기 전에 객체가 존재했다면 반환 값은 기존 객체에 대한 핸들이고 GetLastError 함수는 ERROR_ALREADY_EXISTS
반환합니다.
프로세스 탐색기는 프로세스가 열렸거나 로드한 Handle
및 DLL
정보를 찾기 위해 Microsoft에서 공식적으로 제공하는 도구입니다.
프로세스 탐색기 공식 페이지: https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer
예:
WeChat을 예로 들어 먼저 WeChat을 시작하고 Process Explorer의 기본 인터페이스에서 WeChat.exe
라는 프로세스를 찾아 선택합니다.
그런 다음 Lower Pane
인터페이스에서 Type
Mutant
이고 Name
Sessions1BaseNamedObjects_WeChat_App_Instance_Identity_Mutex_Name
인 Handle
찾습니다.
Close Handle
마우스 오른쪽 버튼으로 클릭하여 핸들을 닫은 후 새 프로세스를 시작할 수 있습니다.
handles = handler . find_handles ( process_ids = [ 10000 ], handle_names = [ r'Sessions1BaseNamedObjects_WeChat_App_Instance_Identity_Mutex_Name' ])
handler . close_handles ( handles )
참조 소스 코드: client/handler/handler.py
tscon
통해 Windows 원격 데스크톱을 닫은 후 대화형 상태를 유지하는 방법 원격 데스크톱을 사용하여 원격 컴퓨터에 연결할 때 원격 데스크톱을 닫으면 컴퓨터가 잠기고 로그인 화면이 표시됩니다. 잠금 모드에서는 컴퓨터에 GUI
없으므로 현재 실행 중이거나 예약된 GUI
테스트가 실패합니다.
GUI
테스트 관련 문제를 방지하려면 tscon
유틸리티를 사용하여 원격 데스크톱 연결을 끊을 수 있습니다. tscon
로그인 화면을 우회하여 원격 컴퓨터의 원래 로컬 세션으로 제어권을 반환합니다. GUI
테스트를 포함하여 원격 컴퓨터의 모든 프로그램은 계속해서 정상적으로 실행됩니다.
tscon
무엇입니까? tscon
은 원격 데스크톱 세션 호스트 서버의 다른 세션에 연결하는 데 사용할 수 있는 Windows 시스템에서 제공하는 도구입니다.
tscon
어떻게 사용하나요? tscon { < sessionID > | < sessionname > } [/dest: < sessionname > ] [/password: < pw > | /password: * ] [/v]
매개변수 | 설명하다 |
---|---|
<sessionID> | 연결할 세션의 ID를 지정합니다. 선택적 /dest:<sessionname> 매개변수가 사용되는 경우 현재 세션의 이름도 지정할 수 있습니다. |
<sessionname> | 연결할 세션의 이름을 지정합니다. |
/대상: <sessionname> | 현재 세션의 이름을 지정합니다. 새 세션에 연결하면 이 세션의 연결이 끊어집니다. 또한 이 매개변수를 사용하여 다른 사용자의 세션을 다른 세션에 연결할 수도 있습니다. |
/비밀번호: <pw> | 연결할 세션을 소유한 사용자의 비밀번호를 지정합니다. 이 비밀번호는 연결하는 사용자가 세션을 소유하지 않은 경우 필요합니다. |
/비밀번호: * | 연결하려는 세션을 소유한 사용자의 비밀번호를 묻는 메시지를 표시합니다. |
/다섯 | 수행 중인 작업에 대한 정보를 표시합니다. |
/? | 명령 프롬프트에 도움말을 표시합니다. |
원격 데스크톱 연결을 끊으려면 원격 컴퓨터(원격 데스크톱 연결 창)에서 관리자로 다음 명령을 실행합니다(예: 명령줄).
%windir% S ystem32 t scon.exe RDP-Tcp# # # NNN /dest:console
여기서 RDP-Tcp### NNN
RDP-Tcp#5
와 같은 현재 원격 데스크톱 세션의 ID
입니다. Windows 작업 관리자 의 사용자 탭에 있는 세션 열에서 이를 확인할 수 있습니다.
원격 데스크톱 서비스 세션이 종료되었다는 메시지가 표시되고 원격 데스크톱 클라이언트가 닫힙니다. 그러나 원격 컴퓨터의 모든 프로그램과 테스트는 계속해서 정상적으로 실행됩니다.
팁: 세션 열은 기본적으로 숨겨져 있습니다. 이를 표시하려면 CPU, 메모리 등이 표시된 행의 아무 곳이나 마우스 오른쪽 버튼으로 클릭하고 열리는 컨텍스트 메뉴에서 세션을 선택합니다.
배치 파일을 사용하여 연결 끊기 프로세스를 자동화할 수 있습니다. 원격 컴퓨터에서 다음을 수행합니다.
for /f " skip=1 tokens=3 " %%s in ( ' query user %USERNAME% ' ) do (
%windir% S ystem32 t scon.exe %%s /dest:console
)
tscon
원격 컴퓨터를 잠금 해제된 상태로 유지하므로 시스템 보안이 저하됩니다. 테스트 실행이 완료된 후 다음 명령을 사용하여 머신을 잠글 수 있습니다.
Rundll32.exe user32.dll, LockWorkStation
원격 컴퓨터에서 rdpclip.exe
프로세스가 실행 중이고 원격 세션 연결을 끊을 때 클립보드가 비어 있지 않으면 rdpclip.exe
프로세스가 실패할 수 있습니다.
이 문제를 방지하려면 세션 연결을 끊기 전에 rdpclip.exe
프로세스를 종료할 수 있습니다.
자동화 기능을 확장해야 하거나 다른 버전의 클라이언트와 호환되어야 하는 경우 rpa-client/app 모듈에서 작업 스크립트를 추가하거나 편집할 수 있습니다.
프로젝트에서는 주로 다음 두 가지 방법을 사용합니다.
피윈오토
Microsoft Windows GUI 자동화를 위한 Python 모듈입니다. 가장 간단한 경우 마우스 및 키보드 작업을 Windows 대화 상자 및 컨트롤로 보낼 수 있지만 텍스트 데이터 가져오기와 같은 더 복잡한 작업도 지원합니다.
에어테스트
NetEase Games에서 출시한 크로스 플랫폼, 이미지 인식 기반 UI 자동화 테스트 프레임워크입니다. 지원되는 플랫폼은 Windows, Android 및 iOS입니다.
사용 중인 운영 체제가 Windows 7 이상인지, Python 버전이 3.7.0 이상인지 확인하세요.
airtest의 현재 버전은 pywinauto==0.6.3
에 의존하며 현재 프로젝트에는 pywinauto==0.6.8
필요합니다. 종속성을 설치할 때 --no-deps
매개변수를 추가하거나 pip install pywinauto==0.6.8
수동으로 실행하세요. pip install pywinauto==0.6.8
.
git clone https://github.com/yihleego/robotic-process-automation.git
cd robotic-process-automation/rpa-client
pip install --no-deps -r requirements.txt
클라이언트 구성 파일은 rpa-client/config.yml에 있으며 개발자는 실제 시나리오에 따라 구성을 수정할 수 있습니다.
재산 | 설명 | 기본 |
---|---|---|
서버.호스트 | 서버 호스트 | 로컬호스트 |
서버.포트 | 서버 포트 | 18888 |
서버.경로 | 서버 경로 | /rpa |
서버.ssl | SSL 활성화 여부 | 거짓 |
앱 크기 | 실행할 수 있는 최대 프로그램 수 | 32 |
app.path. <appid> | 사용자 정의 프로그램 경로 | 레지스트리에서 가져오기 |
airtest.cv전략 | 이미지 인식 알고리즘 | [tpl,sift,brisk] |
에어테스트.타임아웃 | 이미지 인식 알고리즘 | 20초 |
airtest.timeout-tmp | 이미지 인식 알고리즘 | 3초 |
로깅 수준 | 로그 수준 | 디버그 |
로깅.형식 | 로그 형식 | 기본 형식 |
로깅.파일 이름 | 로그 파일 이름 | ./logs/rpa-client.log |
rpa-client/main.py를 실행하세요.
사용 중인 Java 버전이 17 이상인지 확인하세요. 서비스를 실행할 때 MySQL 및 Redis에 따라 달라지므로 서비스를 배포하기 전에 반드시 설치하고 시작하세요.
git clone https://github.com/yihleego/robotic-process-automation.git
cd robotic-process-automation/rpa-server
mvn clean install
재산 | 설명 | 기본 |
---|---|---|
spring.datasource.driver-클래스 이름 | 데이터 소스 기반 | com.mysql.cj.jdbc.Driver |
spring.datasource.url | 데이터 소스 URL | jdbc:mysql://localhost:3306/rpa |
spring.datasource.사용자 이름 | 데이터 소스 사용자 이름 | |
spring.datasource.password | 데이터 소스 비밀번호 | |
spring.data.redis.호스트 | Redis 호스트 | 로컬호스트 |
spring.data.redis.port | 레디스 포트 | 6379 |
spring.data.redis.비밀번호 | 레디스 비밀번호 | |
spring.data.redis.데이터베이스 | 레디스 데이터베이스 | 0 |
위 구성은 application.properties 파일에서 수정할 수 있습니다.
재산 | 설명 | 기본 |
---|---|---|
rpa.websocket.port | WebSocket 서비스 포트 | 18888 |
rpa.websocket.경로 | WebSocket 서비스 경로 | /rpa |
rpa.websocket.idle-timeout | WebSocket 서비스 유휴 시간 초과 | 5m |
rpa.converter.date-time-pattern | 전역 날짜 시간 형식 | yyyy-MM-dd HH:mm:ss |
rpa.converter.date-패턴 | 글로벌 날짜 형식 | yyyy-MM-dd |
rpa.converter.time 패턴 | 글로벌 시간 형식 | HH:mm:ss |
rpa.client.cache-키 | 클라이언트 캐시 키 형식 | rpa:클라이언트: <appid> : <account> |
rpa.client.cache-시간 초과 | 클라이언트 캐시 시간 초과 | 5m |
자세한 내용은 RpaProperties를 참조하세요.
서비스를 시작하기 전에 MySQL
인스턴스에서 다음 스크립트를 실행하십시오.
RpaApplication.java를 실행하면 됩니다.
$(".btn").click();
응용 프로그램이 UIA를 지원하는지 확인하려면 Microsoft에서 공식적으로 제공하는 소프트웨어 Inspect를 사용할 수 있습니다. 이 소프트웨어는 공식 웹사이트나 다음 창고에서 다운로드할 수 있습니다.
예제에서 WeChat은 UIA를 기반으로 구현되었기 때문에 UiaApp 모드를 사용하는 반면, Enterprise WeChat은 웹 페이지에 하나의 Canvas만 있는 것과 유사한 AirApp 모드를 사용합니다. 모든 요소는 코드를 통해 그려지고 렌더링됩니다. 이므로 이미지 인식을 통해서만 위치를 지정할 수 있습니다.
Visual Studio 2013용 Visual C++ 재배포 가능 패키지 다운로드 https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=40784
다음을 참조하세요: 2#issue
본 프로젝트는 학습 참고용이므로 프로덕션 환경에서는 사용하지 마세요.
이 프로젝트는 MIT 라이선스를 따릅니다. 자세한 내용은 LICENSE 파일을 참조하세요.