├── .gitignore
├── README.md
├── app.py
├── config.example.json
└── readme-img
├── mirage.png
└── title.png
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/python
3 | # Edit at https://www.gitignore.io/?templates=python
4 |
5 | ### Python ###
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | pip-wheel-metadata/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | .hypothesis/
56 | .pytest_cache/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 |
67 | # Flask stuff:
68 | instance/
69 | .webassets-cache
70 |
71 | # Scrapy stuff:
72 | .scrapy
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | .python-version
89 |
90 | # celery beat schedule file
91 | celerybeat-schedule
92 |
93 | # SageMath parsed files
94 | *.sage.py
95 |
96 | # Environments
97 | .env
98 | .venv
99 | env/
100 | venv/
101 | ENV/
102 | env.bak/
103 | venv.bak/
104 |
105 | # Spyder project settings
106 | .spyderproject
107 | .spyproject
108 |
109 | # Rope project settings
110 | .ropeproject
111 |
112 | # mkdocs documentation
113 | /site
114 |
115 | # mypy
116 | .mypy_cache/
117 | .dmypy.json
118 | dmypy.json
119 |
120 | # Pyre type checker
121 | .pyre/
122 |
123 | ### Python Patch ###
124 | .venv/
125 |
126 | # config file
127 | config.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 복무봇 (Bokmu-Bot)
2 |
3 |
4 |
5 | 
6 |
7 | 
8 |
9 |
10 |
11 | 복무봇은 제가 군대를 가고 나서도 Slack 을 통해 저와 제 지인들이 제 군복무율을 확인할 수 있도록 (입대 5일전에) 만든 프로젝트 입니다.
12 |
13 | 하루에 한번, **정해진 시간마다 복무일자와 복무율**을 알려줍니다. 만약 아직 입대하지 않았다면, **입대까지 남은 날**을 보여줍니다.
14 |
15 | ## Dependencies
16 |
17 | 아래 모듈을 설치 해야합니다.
18 |
19 | - SlackClient
20 | - dateutil
21 | - schedule
22 |
23 | ## Get Started
24 |
25 | ### Create Your Slack App
26 |
27 | Slack 에서 여러분의 **[App을 생성](https://api.slack.com/apps)** 해주세요.
28 |
29 | ### Add Bot User into Slack
30 |
31 | Basic Information > Add features and functionality 에서 `Bots` 를 클릭합니다.
32 |
33 | 그 다음 `Add a Bot User` 를 클릭하고, Display Name 과 Default username 을 채워 넣고, `Add Bot User` 를 클릭해 여러분 Slack 에 봇 유저를 생성합니다.
34 |
35 | ### Fill config.json
36 |
37 | App 과 Bot User를 생성하셨다면, `config.json` 파일을 `app.py` 와 같은 경로에 생성해줍니다. 그리고 `config.example.json` 파일의 내용을 복사하여 붙여 넣어줍니다. `config.example.json` 의 내용은 다음과 같습니다.
38 |
39 | ```json
40 | {
41 | "slack": {
42 | "apiToken": "Slack App API Token (Bot User OAuth Access Token)",
43 | "clientId": "Slack App Client ID",
44 | "clientSecret": "Slack App Client Secret",
45 | "verificationToken": "Slack App Verification Token",
46 | "channelId": "Slack Channel Id"
47 | },
48 | "bot": {
49 | "yourName": "사용자 이름",
50 | "notifyAt": "슬랙에 매일 메세지를 보낼 시각. 24시간제 시간제로 기입 (오전 7시는 7, 오후 1시는 13)",
51 | "startDate": "yyyy/mm/dd (입대일자)",
52 | "endDate": "yyyy/mm/dd (제대일자)"
53 | }
54 | }
55 | ```
56 |
57 | ### API 정보
58 |
59 | 그 다음 `config.json` 파일을 아래 설명을 읽고 채워 넣어주세요.
60 |
61 | - **clientId, clientSecret, verificationToken**
62 |
63 | Basic Information > App Credentials
64 |
65 | 에서 찾으실 수 있습니다. 해당하는 정보를 입력해줍니다.
66 |
67 | - **apiToken**
68 |
69 | Install App > Install App to Your Team
70 |
71 | 에서 `Install App to Workspace` 를 클릭하고, `Authorize` 를 클릭합니다.
72 |
73 | 그 뒤 **xoxb-** 로 시작하는 `Bot User OAuth Access Token` 의 내용으로 입력해주시면 됩니다.
74 |
75 | - **channelId**
76 |
77 | 복무봇의 메세지가 올라올 채널을 설정합니다.
78 |
79 | **[이 곳](https://www.wikihow.com/Find-a-Channel-ID-on-Slack-on-PC-or-Mac)** 을 클릭하여 원하는 채널의 ID를 알아내세요.
80 |
81 | ### 봇 설정
82 |
83 | - **yourName** : 여러분의 이름을 입력하세요.
84 | - **notifyAt** : 하루에 한번 메세지를 보내는 시각을 설정합니다. 24 시간제로 입력해주세요.
85 | - **startDate** : 군복무 시작일 입니다.
86 | - **endDate** : 전역일 입니다.
87 |
88 | ## Usage
89 |
90 | ```
91 | python app.py
92 | ```
93 |
94 | 명령을 통해 `app.py` 를 실행합니다.
95 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import time
2 | import json
3 | from slackclient import SlackClient
4 | from dateutil import parser
5 | from datetime import datetime
6 | import schedule
7 |
8 | # load configurations
9 | config = json.loads(open("./config.json").read())
10 |
11 | API_TOKEN = config["slack"]["apiToken"]
12 | CLIENT_ID = config["slack"]["clientId"]
13 | CLIENT_SECRET = config["slack"]["clientSecret"]
14 | VERIFICATION_TOKEN = config["slack"]["verificationToken"]
15 | CHANNEL_ID = config["slack"]["channelId"]
16 |
17 | YOUR_NAME = config["bot"]["yourName"]
18 | NOTIFY_AT = config["bot"]["notifyAt"]
19 | START_DATE = parser.parse(config["bot"]["startDate"])
20 | END_DATE = parser.parse(config["bot"]["endDate"])
21 |
22 | START_IMG = "https://i.imgur.com/PkbKJY1.jpg" # 시작 메세지 이미지
23 | DAILY_BEFORE_ENLISTMENT_IMG = "https://i.imgur.com/uM1dyrM.png" # 군입대 전 메세지 이미지
24 | DAILY_AFTER_ENLISTMENT_IMG = "https://i.imgur.com/U059WfE.png" # 군입대 후 메세지 이미지
25 |
26 |
27 | # init slack client
28 | sc = SlackClient(API_TOKEN)
29 |
30 |
31 | def _format_date(date):
32 | return date.strftime("%Y년 %m월 %d일")
33 |
34 |
35 | def _calc_total_days():
36 | # 총 군복무 일 수
37 | total_days = (END_DATE - START_DATE).days
38 | return total_days
39 |
40 |
41 | def _calc_remaining_days():
42 | # 남은 군복무 일 수
43 | remaining_days = (END_DATE - datetime.now()).days
44 | return remaining_days
45 |
46 |
47 | def _calc_after_enlistment_days():
48 | # 군 입대후 지난 일 수. 음수라면 입대 전
49 | after_enlistment_days = (datetime.now() - START_DATE).days
50 | return after_enlistment_days
51 |
52 |
53 | def _calc_percentage():
54 | # 복무율
55 | percentage = round(
56 | (_calc_after_enlistment_days() / _calc_total_days()) * 100, 2)
57 | return percentage
58 |
59 |
60 | def _render_progress_bar():
61 | # 텍스트 프로그레스바
62 | total_squares = 20
63 | filled_squares = round(_calc_percentage() / (100 / total_squares))
64 | unfilled_squares = total_squares - filled_squares
65 |
66 | progress_bar = ""
67 |
68 | for i in range(filled_squares):
69 | progress_bar = progress_bar + "█ "
70 |
71 | for i in range(unfilled_squares):
72 | progress_bar = progress_bar + "▒ "
73 |
74 | return progress_bar
75 |
76 |
77 | def send_start_message():
78 | formatted_start_date = _format_date(START_DATE)
79 | formatted_end_date = _format_date(END_DATE)
80 |
81 | sc.api_call(
82 | "chat.postMessage",
83 | channel=CHANNEL_ID,
84 | blocks=[
85 | {
86 | "type": "section",
87 | "text": {
88 | "type": "mrkdwn",
89 | "text": "복무봇이 시작되었습니다. :sob:"
90 | }
91 | },
92 | {
93 | "type": "divider"
94 | },
95 | {
96 | "type": "section",
97 | "fields": [
98 | {
99 | "type": "mrkdwn",
100 | "text": "*장병이름*\n{}".format(YOUR_NAME)
101 | },
102 | {
103 | "type": "mrkdwn",
104 | "text": "*알림시각*\n{}시".format(NOTIFY_AT)
105 | },
106 | {
107 | "type": "mrkdwn",
108 | "text": "*복무 시작일*\n{}".format(formatted_start_date)
109 | },
110 | {
111 | "type": "mrkdwn",
112 | "text": "*복무 종료일*\n{}".format(formatted_end_date)
113 | }
114 | ],
115 | "accessory": {
116 | "type": "image",
117 | "image_url": START_IMG,
118 | "alt_text": "plants"
119 | }
120 | },
121 | {
122 | "type": "context",
123 | "elements": [
124 | {
125 | "type": "plain_text",
126 | "text": "Developed by Hudi (https://hudi.kr)",
127 | }
128 | ]
129 | }
130 | ]
131 | )
132 |
133 |
134 | def _send_daily_message_before_enlistment():
135 | formatted_today_date = _format_date(datetime.now())
136 | formatted_start_date = _format_date(START_DATE)
137 | formatted_end_date = _format_date(END_DATE)
138 |
139 | total_days = _calc_total_days()
140 | after_enlistment_days = _calc_after_enlistment_days()
141 |
142 | sc.api_call(
143 | "chat.postMessage",
144 | channel=CHANNEL_ID,
145 | blocks=[
146 | {
147 | "type": "section",
148 | "text": {
149 | "type": "mrkdwn",
150 | "text": "`{}` 오늘, *{}* 님은 입대까지 {}일 남았습니다.\n\n".format(formatted_today_date, YOUR_NAME, -after_enlistment_days)
151 | }
152 | },
153 | {
154 | "type": "divider"
155 | },
156 | {
157 | "type": "section",
158 | "fields": [
159 | {
160 | "type": "mrkdwn",
161 | "text": "*복무 시작일*\n{}".format(formatted_start_date)
162 | },
163 | {
164 | "type": "mrkdwn",
165 | "text": "*복무 종료일*\n{}".format(formatted_end_date)
166 | },
167 | {
168 | "type": "mrkdwn",
169 | "text": "*총 복무일*\n{}일".format(total_days)
170 | },
171 | {
172 | "type": "mrkdwn",
173 | "text": "*입대까지*\n{}일".format(-after_enlistment_days)
174 | }
175 | ],
176 | "accessory": {
177 | "type": "image",
178 | "image_url": DAILY_BEFORE_ENLISTMENT_IMG,
179 | "alt_text": "plants"
180 | }
181 | },
182 | {
183 | "type": "context",
184 | "elements": [
185 | {
186 | "type": "plain_text",
187 | "text": "Developed by Hudi (https://hudi.kr)",
188 | "emoji": True
189 | }
190 | ]
191 | }
192 | ]
193 | )
194 |
195 |
196 | def _send_daily_message_after_enlistment():
197 | formatted_today_date = _format_date(datetime.now())
198 | formatted_start_date = _format_date(START_DATE)
199 | formatted_end_date = _format_date(END_DATE)
200 |
201 | percentage = _calc_percentage()
202 | progress_bar = _render_progress_bar()
203 | total_days = _calc_total_days()
204 | after_enlistment_days = _calc_after_enlistment_days()
205 | remaining_days = _calc_remaining_days()
206 |
207 | sc.api_call(
208 | "chat.postMessage",
209 | channel=CHANNEL_ID,
210 | blocks=[
211 | {
212 | "type": "section",
213 | "text": {
214 | "type": "mrkdwn",
215 | "text": "`{}` 오늘, *{}* 님의 복무율 \n\n 복무율 *{}%* | {} | *{}일 / {}일*".format(formatted_today_date, YOUR_NAME, percentage, progress_bar, after_enlistment_days, total_days)
216 | }
217 | },
218 | {
219 | "type": "divider"
220 | },
221 | {
222 | "type": "section",
223 | "fields": [
224 | {
225 | "type": "mrkdwn",
226 | "text": "*복무 시작일*\n{}".format(formatted_start_date)
227 | },
228 | {
229 | "type": "mrkdwn",
230 | "text": "*복무 종료일*\n{}".format(formatted_end_date)
231 | },
232 | {
233 | "type": "mrkdwn",
234 | "text": "*총 복무일*\n{}일".format(total_days)
235 | },
236 | {
237 | "type": "mrkdwn",
238 | "text": "*현재 복무일*\n{}일".format(after_enlistment_days)
239 | },
240 | {
241 | "type": "mrkdwn",
242 | "text": "*남은 복무일*\n{}일".format(remaining_days)
243 | }
244 | ],
245 | "accessory": {
246 | "type": "image",
247 | "image_url": DAILY_AFTER_ENLISTMENT_IMG,
248 | "alt_text": "plants"
249 | }
250 | },
251 | {
252 | "type": "context",
253 | "elements": [
254 | {
255 | "type": "plain_text",
256 | "text": "Developed by Hudi (https://hudi.kr)",
257 | "emoji": True
258 | }
259 | ]
260 | }
261 | ]
262 | )
263 |
264 |
265 | def send_daily_message():
266 | after_enlistment_days = _calc_after_enlistment_days()
267 | if (after_enlistment_days < 0):
268 | _send_daily_message_before_enlistment()
269 | else:
270 | _send_daily_message_after_enlistment()
271 |
272 | print("[복무봇] 현재 복무율 전송")
273 |
274 |
275 | if __name__ == '__main__':
276 | notify_at = "{}:00".format(str(NOTIFY_AT).zfill(2))
277 |
278 | print("[복무봇] 실행 준비중...")
279 |
280 | send_start_message()
281 | print("[복무봇] 실행 메세지 전송")
282 |
283 | send_daily_message()
284 | print("[복무봇] 현재 복무율 최초 전송")
285 | # 봇 실행시 최초 실행
286 |
287 | # 스케줄 시작
288 | schedule.every().day.at(notify_at).do(send_daily_message)
289 | print("[복무봇] 복무봇 스케줄 설정: {} 마다 실행".format(notify_at))
290 |
291 | print("[복무봇] 스케줄링 시작!")
292 | while True:
293 | schedule.run_pending()
294 | time.sleep(1)
295 |
--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "slack": {
3 | "apiToken": "Slack App API Token (Bot User OAuth Access Token)",
4 | "clientId": "Slack App Client ID",
5 | "clientSecret": "Slack App Client Secret",
6 | "verificationToken": "Slack App Verification Token",
7 | "channelId": "Slack Channel Id"
8 | },
9 | "bot": {
10 | "yourName": "사용자 이름",
11 | "notifyAt": "슬랙에 매일 메세지를 보낼 시각. 24시간제 시간제로 기입 (오전 7시는 7, 오후 1시는 13)",
12 | "startDate": "yyyy/mm/dd (입대일자)",
13 | "endDate": "yyyy/mm/dd (제대일자)"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/readme-img/mirage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devHudi/bokmu-bot/784acac8cf04d18ab53cd0d028885ed8f238e0df/readme-img/mirage.png
--------------------------------------------------------------------------------
/readme-img/title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devHudi/bokmu-bot/784acac8cf04d18ab53cd0d028885ed8f238e0df/readme-img/title.png
--------------------------------------------------------------------------------