Skip to content

Commit

Permalink
feat: Email 정보 입력 기능 추가 (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
roeniss authored Feb 12, 2024
2 parents 7f5a552 + 4d62f44 commit abfdebe
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 39 deletions.
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ dhapi buy_lotto645 -q # 자동모드로 5장 구매

## 구현된 기능

- [로또 6/45](https://dhlottery.co.kr/gameInfo.do?method=gameMethod&wiselog=H_B_1_1)
- 자동 구매 1 ~ 5장
- [로또 6/45](https://dhlottery.co.kr/gameInfo.do?method=gameMethod&wiselog=H_B_1_1)
- 자동 구매 1 ~ 5장

## 고급 설정

Expand All @@ -34,6 +34,35 @@ password = "dhlotter_second_pw"

이후 `-p` 플래그로 프로필을 골라 사용합니다.

### 이메일로 결과 전송 하기

`-e` 플래그로 수신할 이메일을 지정합니다. 이렇게 하면 **콘솔에 결과가 출력되지 않고 지정한 이메일로 전송됩니다.** 아래 세팅이 추가적으로 필요합니다.

무료로 이메일을 보내기 위해 [Mailjet](https://www.mailjet.com/)을 사용합니다. 가입한 후, API KEY, SECRET KEY 를 발급합니다 (https://app.mailjet.com/account/apikeys).

키 정보를 ~/.dhapi/credentials 파일에 다음과 같이 기입합니다.

```text
[default]
username = "dhlotter_id"
password = "dhlotter_pw"
mailjet_api_key = "YOUR_API_KEY"
mailjet_api_secret = "YOUR_SECRET_KEY"
mailjet_sender_email = "YOUR_MAILJET_EMAIL"
[another_profile]
...
```

필요한 프로필에만 세팅하면 됩니다.

> [!IMPORTANT]
> 위 세팅대로 따라온다면 결과 이메일이 아주 높은 확률로 스팸 메일함에 들어갑니다. 이럴 경우 해당 메일을 찾아서 '스팸이 아님' 체크를 해야 이후 메일들이 일반 메일함에 들어갑니다.
> [!WARNING]
> `mailjet_sender_email` 값은 '발신 이메일 주소'로 활용되며, Mailjet 회원가입에 사용한 이메일이 아닐 경우 추가 세팅을 해야됩니다.
>
> 따로 세팅을 하지 않은 상태로 별도의 이메일을 기입하게 되면, 실제 메일이 발송되지 않고 'Senders and domains page'를 확인하라는 안내 메일을 받게 됩니다.
## 기여하기

기여는 대환영입니다! [CONTRIBUTING.md](/docs/CONTRIBUTING.md) 파일을 참고해주세요.
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ tomli==2.0.1
urllib3==1.26.18
webencodings==0.5.1
tomli-w==1.0.0
mailjet-rest==1.3.4
10 changes: 6 additions & 4 deletions src/dhapi/client/lottery_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class LotteryClient:
_round_info_url = "https://www.dhlottery.co.kr/common.do?method=main"
_ready_socket = "https://ol.dhlottery.co.kr/olotto/game/egovUserReadySocket.json"

def __init__(self):
def __init__(self, user_id: str, user_pw: str):
self.user_id = user_id
self.user_pw = user_pw
self._headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
"Connection": "keep-alive",
Expand Down Expand Up @@ -56,14 +58,14 @@ def _set_default_session(self):
else:
raise RuntimeError("JSESSIONID 쿠키가 정상적으로 세팅되지 않았습니다.")

def login(self, user_id: str, user_pw: str):
def login(self):
resp = requests.post(
LotteryClient._login_request_url,
headers=self._headers,
data={
"returnUrl": LotteryClient._main_url,
"userId": user_id,
"password": user_pw,
"userId": self.user_id,
"password": self.user_pw,
"checkSave": "off",
"newsEventYn": "",
},
Expand Down
34 changes: 34 additions & 0 deletions src/dhapi/client/mailjet_email_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging

from mailjet_rest import Client

logger = logging.getLogger(__name__)


class MailjetEmailClient:
def __init__(self, to_email, api_key, api_secret, _sender_email):
self._to_email = to_email
self._mailjet = Client(auth=(api_key, api_secret), version="v3.1")
self._sender_email = _sender_email

def send_email(self, subject, body):
data = {
"Messages": [
{
"From": {"Email": self._sender_email, "Name": "동행복권 API"},
"To": [{"Email": self._to_email, "Name": self._to_email}],
"Subject": subject,
"TextPart": body,
"HTMLPart": "",
}
]
}

result = self._mailjet.send.create(data=data)
logging.debug(result.json())

if result.status_code == 200:
logger.info("메일 전송에 성공했습니다.")
else:
logger.error(f"메일 전송에 실패했습니다. (result: {result.json()})")
raise RuntimeError(f"메일 전송에 실패했습니다. (status_code: {result.status_code})")
34 changes: 20 additions & 14 deletions src/dhapi/purchase/lotto645_controller.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import logging

from dhapi.client.lottery_client import LotteryClient
from dhapi.client.mailjet_email_client import MailjetEmailClient
from dhapi.domain_object.lotto645_buy_request import Lotto645BuyRequest

logger = logging.getLogger(__name__)


class Lotto645Controller:
def __init__(self, user_id, user_pw):
self.client = LotteryClient()
self.client.login(user_id, user_pw)
def __init__(self, lottery_client: LotteryClient, email_client: MailjetEmailClient):
self.client = lottery_client
self.client.login()
self.email_client = email_client

def buy(self, req: Lotto645BuyRequest, quiet: bool):
def buy(self, req: Lotto645BuyRequest, quiet: bool = False, send_result_to_email: bool = False):
if not self._confirm_purchase(req, quiet):
print("✅ 구매를 취소했습니다.")
else:
result = self.client.buy_lotto645(req)
self._show_result(result)
logger.info("✅ 구매를 취소했습니다.")
return

result = self.client.buy_lotto645(req)

result_text = self._make_result_text(result)
if send_result_to_email:
self.email_client.send_email("동행복권 645 로또 구매 결과", result_text)
return
logger.info(result_text)

def _confirm_purchase(self, req, quiet):
print(
Expand All @@ -26,23 +34,22 @@ def _confirm_purchase(self, req, quiet):
)

if quiet:
print("yes\n✅ --quiet 플래그가 주어져 자동으로 구매를 진행합니다.")
logger.info("yes\n✅ --quiet 플래그가 주어져 자동으로 구매를 진행합니다.")
return True
else:
answer = input().strip().lower()
return answer in ["y", "yes", ""]

# ID가 다른 경우 loginYn이 N으로 나옴
def _show_result(self, body: dict) -> None:
def _make_result_text(self, body: dict) -> str:
result = body.get("result", {})
if result.get("resultMsg", "FAILURE").upper() != "SUCCESS":
logger.debug(f"d: {body}")
raise RuntimeError(f'구매에 실패했습니다: {result.get("resultMsg", "resultMsg is empty")}')

logger.debug(f"response body: {body}")

print(
f"""✅ 구매를 완료하였습니다.
return f"""✅ 구매를 완료하였습니다.
[Lotto645 Buy Response]
------------------
Round:\t\t{result["buyRound"]}
Expand All @@ -51,9 +58,8 @@ def _show_result(self, body: dict) -> None:
Numbers:\n{self._format_lotto_numbers(result["arrGameChoiceNum"])}
Message:\t{result["resultMsg"]}
----------------------"""
)

def _format_lotto_numbers(self, lines: list) -> None:
def _format_lotto_numbers(self, lines: list) -> str:
modes = {
"1": "수동",
"2": "반자동",
Expand Down
47 changes: 37 additions & 10 deletions src/dhapi/router/arg_parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import argparse
import getpass
import sys
from dhapi.router.credentials_provider import get_credentials
from dhapi.router.version_provider import get_installed_version

from dhapi.domain_object.lotto645_buy_request import Lotto645BuyRequest
from dhapi.router.credentials_provider import get_credentials
from dhapi.router.version_provider import get_installed_version


class HelpOnErrorParser(argparse.ArgumentParser):
Expand All @@ -30,7 +30,7 @@ def __init__(self):
[buy_lotto645 명령어 사용 예시]
dhapi buy_lotto645
\t\t\t# ~/.dhapi_profile 을 읽어 ID/PW 를 자동으로 입력받고, 확인 후 자동모드로 5장 구매 (프로필 파일 포맷은 README.md 참고)
\t\t\t# ~/.dhapi/credentials 을 읽어 ID/PW 를 자동으로 입력받고, 확인 후 자동모드로 5장 구매 (프로필 파일 포맷은 README.md 참고)
dhapi buy_lotto645 -q
\t\t\t# 확인 절차 없이 자동모드로 5장 구매
dhapi buy_lotto645 -u $USER_ID
Expand All @@ -45,13 +45,6 @@ def __init__(self):

buy_lotto645.formatter_class = argparse.RawTextHelpFormatter

# -u
# deprecated
buy_lotto645.add_argument("-u", "--username", required=False, help="동행복권 아이디입니다. (deprecated; -p 옵션 사용 권장)")

# -q
buy_lotto645.add_argument("-q", "--quiet", action="store_true", help="플래그 설정 시 구매 전 확인 절차를 스킵합니다.") # "store_true" means "set default to False"

# -g
buy_lotto645.add_argument(
"-g",
Expand All @@ -78,8 +71,19 @@ def __init__(self):
help="지정하지 않으면 'default' 프로필을 사용합니다.",
)

# -u
# deprecated
buy_lotto645.add_argument("-u", "--username", required=False, help="동행복권 아이디입니다. (deprecated; -p 옵션 사용 권장)")

# -q
buy_lotto645.add_argument("-q", "--quiet", action="store_true", help="플래그 설정 시 구매 전 확인 절차를 스킵합니다.") # "store_true" means "set default to False"

# -e
buy_lotto645.add_argument("-e", "--email", required=False, help="구매 결과를 콘솔로 출력하는 대신, 입력된 이메일로 전송합니다.")

# -d
buy_lotto645.add_argument("-d", "--debug", action="store_true", help="로그 출력 레벨을 debug로 세팅합니다.") # "store_true" means "set default to False"

self._args = parser.parse_args()

if self._args.username:
Expand All @@ -90,6 +94,14 @@ def __init__(self):
self._args.username = credentials.get("username")
self._args.password = credentials.get("password")

if self._args.email:
credentials = get_credentials(self.profile())
self._args.mailjet_api_key = credentials.get("mailjet_api_key")
self._args.mailjet_api_secret = credentials.get("mailjet_api_secret")
self._args.mailjet_sender_email = credentials.get("mailjet_sender_email")
if not self._args.mailjet_api_key or not self._args.mailjet_api_secret or not self._args.mailjet_sender_email:
raise RuntimeError("Mailjet API Key/Secret 정보가 없습니다.")

def profile(self):
return self._args.profile

Expand All @@ -102,6 +114,21 @@ def user_id(self):
def user_pw(self):
return self._args.password

def email(self):
return self._args.email

def mailjet_api_key(self):
return self._args.mailjet_api_key

def mailjet_api_secret(self):
return self._args.mailjet_api_secret

def mailjet_sender_email(self):
return self._args.mailjet_sender_email

def send_result_to_email(self):
return self.email() is not None

def is_quiet(self):
return self._args.quiet

Expand Down
8 changes: 6 additions & 2 deletions src/dhapi/router/router.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from dhapi.client.lottery_client import LotteryClient
from dhapi.client.mailjet_email_client import MailjetEmailClient
from dhapi.purchase.lotto645_controller import Lotto645Controller
from dhapi.router.arg_parser import ArgParser
from dhapi.configuration.logger import set_logger
Expand All @@ -9,7 +11,9 @@ def entrypoint():
set_logger(arg_parser.is_debug())

if arg_parser.command() == "BUY_LOTTO645":
ctrl = Lotto645Controller(arg_parser.user_id(), arg_parser.user_pw())
ctrl.buy(arg_parser.buy_lotto645_req(), arg_parser.is_quiet())
lottery_client = LotteryClient(arg_parser.user_id(), arg_parser.user_pw())
email_client = MailjetEmailClient(arg_parser.email(), arg_parser.mailjet_api_key(), arg_parser.mailjet_api_secret(), arg_parser.mailjet_sender_email())
ctrl = Lotto645Controller(lottery_client, email_client)
ctrl.buy(arg_parser.buy_lotto645_req(), arg_parser.is_quiet(), arg_parser.send_result_to_email())
else:
raise NotImplementedError("Not implemented yet")
7 changes: 0 additions & 7 deletions src/dhapi/user_info/user_info_controller.py

This file was deleted.

File renamed without changes.
18 changes: 18 additions & 0 deletions tests/test_arg_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import sys
from unittest import TestCase
from unittest.mock import patch

from dhapi.router.arg_parser import ArgParser


class TestArgParser(TestCase):
@patch("dhapi.router.arg_parser.get_credentials", return_value={"username": "test_user", "password": "test_pw"})
@patch.object(sys, 'argv', ["dhapi", "buy_lotto645", "-e", "[email protected]"])
def test_given_email_flag_is_set_and_api_credentials_not_set_when_arg_parser_init_then_raise_runtime_error(self, get_credentials_mock):
self.assertRaises(RuntimeError, ArgParser)

@patch("dhapi.router.arg_parser.get_credentials", return_value={"mailjet_api_key": "test_key", "mailjet_api_secret": "test_secret", "mailjet_sender_email": "[email protected]"})
@patch.object(sys, 'argv', ["dhapi", "buy_lotto645", "-e", "[email protected]"])
def test_given_email_flag_is_set_and_api_credentials_set_when_arg_parser_init_then_raise_runtime_error(self, get_credentials_mock):
ArgParser()
# no error means success
12 changes: 12 additions & 0 deletions tests/test_mailjet_email_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import unittest

from dhapi.client.mailjet_email_client import MailjetEmailClient


class TestMailjetEmailClient(unittest.TestCase):

@unittest.skip("sending real email is not needed after checking success")
def test_send_email(self):
sut = MailjetEmailClient("EMAIL", "API_KEY", "SECRET_KEY")

sut.send_email("hello", "world")

0 comments on commit abfdebe

Please sign in to comment.