├── src ├── parser │ ├── date.ts │ ├── data │ │ ├── date.json │ │ └── weather.json │ ├── index.ts │ ├── weather.ts │ └── news.ts ├── app.ts └── lib │ ├── slack.ts │ └── discord.ts ├── dist ├── parser │ ├── data │ │ ├── date.json │ │ └── weather.json │ ├── date.js │ ├── weather.js │ ├── index.js │ └── news.js ├── lib │ ├── discord.js │ └── slack.js └── app.js ├── package.json ├── LICENSE ├── .github └── workflows │ └── nodejs.yml ├── README.md ├── .gitignore └── tsconfig.json /src/parser/date.ts: -------------------------------------------------------------------------------- 1 | import dateData from './data/date.json'; 2 | 3 | export const parser = (): string => { 4 | const date = new Date(); 5 | const today = `${date.getMonth() + 1}-${date.getDate()}`; 6 | 7 | console.log('✅ 날짜 파싱 완료'); 8 | 9 | return ( dateData)[today] || null; 10 | }; -------------------------------------------------------------------------------- /src/parser/data/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "2-14": "발렌타인 데이", 3 | "3-4": "화이트 데이", 4 | "3-1": "삼일절", 5 | "4-5": "식목일", 6 | "4-11": "대한민국 임시 정부 수립 기념일", 7 | "5-5": "어린이날", 8 | "5-8": "어버이날", 9 | "5-15": "스승의 날", 10 | "6-25": "6.25 전쟁일", 11 | "10-1": "국군의 날", 12 | "11-11": "농업인의 날", 13 | "12-25": "크리스마스" 14 | } -------------------------------------------------------------------------------- /dist/parser/data/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "2-14": "발렌타인 데이", 3 | "3-4": "화이트 데이", 4 | "3-1": "삼일절", 5 | "4-5": "식목일", 6 | "4-11": "대한민국 임시 정부 수립 기념일", 7 | "5-5": "어린이날", 8 | "5-8": "어버이날", 9 | "5-15": "스승의 날", 10 | "6-25": "6.25 전쟁일", 11 | "10-1": "국군의 날", 12 | "11-11": "농업인의 날", 13 | "12-25": "크리스마스" 14 | } 15 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | import * as weather from './weather'; 2 | import * as news from './news'; 3 | import * as date from './date'; 4 | 5 | const parser = async() => { 6 | const weatherContent = await weather.parse(); 7 | const newsContent = await news.parse(); 8 | const dateContent = date.parser(); 9 | 10 | return { 11 | weather: weatherContent, 12 | news: newsContent, 13 | date: dateContent, 14 | }; 15 | }; 16 | 17 | export default parser; -------------------------------------------------------------------------------- /dist/parser/date.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var date_json_1 = __importDefault(require("./data/date.json")); 7 | exports.parser = function () { 8 | var date = new Date(); 9 | var today = date.getMonth() + 1 + "-" + date.getDate(); 10 | console.log('✅ 날짜 파싱 완료'); 11 | return date_json_1.default[today] || null; 12 | }; 13 | -------------------------------------------------------------------------------- /src/parser/weather.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import axios from 'axios'; 3 | import cheerio from 'cheerio'; 4 | 5 | import weatherData from './data/weather.json'; 6 | 7 | export const parse = async() => { 8 | const token = process.env.WEATHER_API_KEY; 9 | const city = 'Busan'; 10 | 11 | const response = await axios.get(`http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${token}&units=metric`); 12 | const data = response.data; 13 | 14 | console.log('✅ 날씨 파싱 완료'); 15 | 16 | return { 17 | weather: ( weatherData)[data.weather[0].id], 18 | temp: `(${data.main.temp_min}도 ~ ${data.main.temp_max}도)` 19 | }; 20 | }; -------------------------------------------------------------------------------- /src/parser/news.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import cheerio from 'cheerio'; 3 | 4 | export const parse = async() => { 5 | const response = await axios.get('https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko'); // parsing new at google news 6 | 7 | const html: string = response.data; 8 | const $ = cheerio.load(html, { xmlMode: true }); 9 | 10 | const titles: string[] = $('item > title').map((i, element) => $(element).text()).get(); 11 | const links: string[] = $('item > link').map((i, element) => $(element).text()).get(); 12 | 13 | let discordContent = ''; 14 | let slackContent = ''; 15 | 16 | for (let i = 0; i < 3; i++){ 17 | discordContent += `[${titles[i]}](${links[i]})\n`; 18 | slackContent += `<${links[i]}|${titles[i]}>\n`; 19 | } 20 | 21 | console.log('✅ 뉴스 파싱 완료'); 22 | 23 | return { 24 | discordContent, 25 | slackContent, 26 | }; 27 | } -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import parser from './parser'; 3 | import discord from './lib/discord'; 4 | import slack from './lib/slack'; 5 | 6 | (async () => { 7 | const WEBHOOKS = process.env.WEBHOOKS; 8 | if (WEBHOOKS == null) throw new Error('웹훅 리스트를 찾을 수 없어요.'); 9 | 10 | const webhookList = WEBHOOKS.split(','); 11 | 12 | const parsed = await parser(); 13 | 14 | webhookList.map(async url => { 15 | if (url.includes('discord.com') || url.includes("discordapp.com")) { // discord webhook 16 | await discord({ 17 | weather: parsed.weather, 18 | news: parsed.news.discordContent, 19 | date: parsed.date, 20 | 21 | url, 22 | }); 23 | } else if (url.includes('hooks.slack.com')) { //slack webhook 24 | await slack({ 25 | weather: parsed.weather, 26 | news: parsed.news.slackContent, 27 | date: parsed.date, 28 | 29 | url, 30 | }) 31 | } 32 | }); 33 | 34 | console.log('✅ 웹훅 발송 완료'); 35 | })().catch(e => { 36 | console.error(e); 37 | core.setFailed(e); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "letterbot", 3 | "version": "1.0.0", 4 | "description": "github action 으로 만든 디스코드, 슬랙에 아침 8시에 브리핑 해주는 봇입니다.", 5 | "main": "app.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc", 9 | "start": "node dist/app.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/cjaewon/letterbot.git" 14 | }, 15 | "keywords": [ 16 | "letterbot", 17 | "github", 18 | "ation", 19 | "discord", 20 | "slack", 21 | "letter" 22 | ], 23 | "author": "cjaewon", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/cjaewon/letterbot/issues" 27 | }, 28 | "homepage": "https://github.com/cjaewon/letterbot#readme", 29 | "dependencies": { 30 | "@actions/core": "^1.2.6", 31 | "@slack/webhook": "^5.0.3", 32 | "@types/axios": "^0.14.0", 33 | "@types/cheerio": "^0.22.16", 34 | "@types/node": "^13.9.1", 35 | "axios": "^0.21.1", 36 | "cheerio": "^1.0.0-rc.3", 37 | "ts-node": "^8.6.2", 38 | "typescript": "^3.8.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 HEY_CJAEWON 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | # Only Test 8 | # push: 9 | # branches: 10 | # - master 11 | schedule: 12 | # 아침 8시는 UTC 로는 11pm 13 | - cron: "0 23 * * *" 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | node-version: [12.x] 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - uses: actions/cache@v2 32 | with: 33 | path: ~/.npm 34 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 35 | restore-keys: | 36 | ${{ runner.os }}-node- 37 | 38 | - name: Running the bot 🔥 39 | env: 40 | CI: true 41 | WEBHOOKS: ${{ secrets.WEBHOOKS }} 42 | WEATHER_API_KEY: ${{ secrets.WEATHER_API_KEY }} 43 | run: | 44 | npm install 45 | npm run build 46 | npm start 47 | 48 | -------------------------------------------------------------------------------- /src/lib/slack.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | interface slackArgs { 4 | weather: { 5 | weather: string; 6 | temp: string; 7 | }; 8 | news: string; 9 | date: string; 10 | 11 | url: string; 12 | } 13 | 14 | export default async({ weather, news, date, url }: slackArgs) => { 15 | const today = new Date().toLocaleDateString().replace(/\. /g, '-').replace('.', ''); 16 | 17 | let message: any = { 18 | attachments: [], 19 | }; 20 | 21 | message.attachments.push({ 22 | color: '#928BFF', 23 | pretext: `📨 ${today} 편지가 왔어요!`, 24 | 25 | fields: [ 26 | { 27 | title: '📅 날짜 / 한국', 28 | value: `${today} ${date ? '(' + date + ')' : ''}`, 29 | short: true, 30 | }, 31 | { 32 | title: '🏞️ 날씨 / 부산', 33 | value: weather.weather, 34 | short: true, 35 | }, 36 | { 37 | title: '🌡 온도 / 부산', 38 | value: weather.temp, 39 | short: true, 40 | }, 41 | ], 42 | 43 | footer: '제작: 재웜', 44 | footer_icon: 'https://images-ext-2.discordapp.net/external/GyQicPLz_zQO15bOMtiGTtC4Kud7JjQbs1Ecuz7RrtU/https/cdn.discordapp.com/embed/avatars/1.png', 45 | }); 46 | 47 | message.attachments.push({ 48 | // text: '', 49 | fields: [ 50 | { 51 | type: 'mrkdwn', 52 | title: '📰 뉴스 / 구글', 53 | value: news, 54 | } 55 | ], 56 | }); 57 | 58 | await axios.post(url, message); 59 | }; -------------------------------------------------------------------------------- /src/lib/discord.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | interface discordArgs { 4 | weather: { 5 | weather: string; 6 | temp: string; 7 | }; 8 | news: string; 9 | date: string; 10 | 11 | url: string; 12 | } 13 | 14 | export default async({ weather, news, date, url }: discordArgs) => { 15 | const today = new Date().toLocaleDateString().replace(/\. /g, '-').replace('.', ''); 16 | 17 | let message: any = { 18 | username: '편지봇', 19 | avatar_url: 'https://cdn.discordapp.com/attachments/683175932873539589/689459371151065088/message-3592640_1280.jpg', 20 | content: `📨 ${today} 편지가 왔어요!`, 21 | embeds: [], 22 | }; 23 | 24 | message.embeds.push({ 25 | color: 0x928BFF, 26 | fields: [ 27 | { 28 | name: '📅 날짜 / 한국', 29 | value: `${today} ${date ? '(' + date + ')' : ''}`, 30 | inline: true 31 | }, 32 | { 33 | name: '🏞️ 날씨 / 부산', 34 | value: weather.weather, 35 | inline: true 36 | }, 37 | { 38 | name: '🌡 온도 / 부산', 39 | value: weather.temp, 40 | inline: true 41 | } 42 | ], 43 | footer: { 44 | text: '제작자 : 재웜', 45 | icon_url: 'https://images-ext-2.discordapp.net/external/GyQicPLz_zQO15bOMtiGTtC4Kud7JjQbs1Ecuz7RrtU/https/cdn.discordapp.com/embed/avatars/1.png' 46 | }, 47 | }); 48 | 49 | message.embeds.push({ 50 | color: 0x928BFF, 51 | title: '📰 뉴스 / 구글', 52 | description: news 53 | }); 54 | 55 | await axios.post(url, message); 56 | }; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 편지봇 2 | 📨 Github Action 으로 만든 디스코드, 슬랙에 매일 아침 8시에 브리핑을 해주는 봇입니다 3 | 4 | ![Node.js CI](https://github.com/cjaewon/letterbot/workflows/Node.js%20CI/badge.svg) 5 | ![cjaewon/letterbot License](https://img.shields.io/github/license/cjaewon/letterbot?color=blue) 6 | ![HitCount](http://hits.dwyl.com/cjaewon/letterbot.svg) 7 | ![stars](https://img.shields.io/github/stars/cjaewon/letterbot?color=yellow&style=social) 8 | 9 | ### 사용 예시 10 | 11 | #### 디스코드 웹훅 12 | ![image](https://user-images.githubusercontent.com/32125218/77283471-c24d3c00-6d0f-11ea-8635-d7120a24d344.png) 13 | 14 | #### 슬랙 웹훅 15 | ![image](https://user-images.githubusercontent.com/32125218/77283427-aa75b800-6d0f-11ea-9f29-d694dec8830b.png) 16 | 17 | ### ⚙️ 설정법 18 | **cjaewon/letterbot을 먼저 Fork 해주세요. / 또는 클론 하셔서 레포를 만드셔도 됩니다.**. 19 | 봇 작동을 위해서는 깃허브 secrets에 WEATHER_API_KEY 와 WEBHOOKS가 필요합니다 20 | 21 | ![image](https://user-images.githubusercontent.com/32125218/76913894-b28ebb80-68fb-11ea-978c-57ffe17dd2bc.png) 22 | 23 | #### WEATHER_API 24 | WEATHER_API_KEY는 [Weather Api](https://openweathermap.org/api) 에서 생성이 가능해요. 25 | 26 | #### WEBHOOKS 27 | WEBHOOKS는 웹훅 링크를 콤마로 구분한 것입니다 28 | ##### 한개 일때 29 | `https://webhooklinkexample.letterbot` 30 | ##### 두개 이상 31 | ``` 32 | https://webhooklinkexample1.letterbot, 33 | https://webhooklinkexample2.letterbot, 34 | https://webhooklinkexample3.letterbot, 35 | https://webhooklinkexample4.letterbot 36 | ``` 37 | 이렇게 콤마로 구분해주시면 됩니다. 38 | 39 | 그럼 인제 매일 아침 8시마다 디스코드 또는 슬랙에 메세지를 보내드립니다 40 | 만약 시간을 수정하고 싶으시면 `.github/workflows/nodejs.yml` cron 시간을 변경하시면 됩니다 (UTC 기준입니다.) 41 | -------------------------------------------------------------------------------- /src/parser/data/weather.json: -------------------------------------------------------------------------------- 1 | { 2 | "200":"🌧 가벼운 비를 동반한 천둥구름", 3 | "201":"🌧 비를 동반한 천둥구름", 4 | "202":"🌩 폭우를 동반한 천둥구름", 5 | "210":"🌩 약한 천둥구름", 6 | "211":"🌩 천둥구름", 7 | "212":"🌩 강한 천둥구름", 8 | "221":"🌩 불규칙적 천둥구름", 9 | "230":"🌩 약한 연무를 동반한 천둥구름", 10 | "231":"🌩 연무를 동반한 천둥구름", 11 | "232":"🌧 강한 안개비를 동반한 천둥구름", 12 | "300":"🌧 가벼운 안개비가 내려요", 13 | "301":"🌧 안개비가 내려요", 14 | "302":"🌧 강한 안개비가 내려요", 15 | "310":"🌧 가벼운 적은비가 내려요", 16 | "311":"🌧 적은비가 내려요", 17 | "312":"🌧 강한 적은비가 내려요", 18 | "313":"🌧 소나기와 안개비", 19 | "314":"🌧 강한 소나기와 안개비", 20 | "321":"🌧 소나기가 내려요", 21 | "500":"🌧 약한 비가 내려요", 22 | "501":"🌧 중간 비가 내려요", 23 | "502":"🌧 강한 비가 내려요", 24 | "503":"🌧 매우 강한 비가 내려요", 25 | "504":"🌧 극심한 비가 내려요", 26 | "511":"🌧 우박이 떨어져요", 27 | "520":"🌧 약한 소나기 비가 내려요", 28 | "521":"🌧 소나기 비가 내려요", 29 | "522":"🌧 강한 소나기 비가 내려요", 30 | "531":"🌧 불규칙적 소나기 비가 내려요", 31 | "600":"❄ 가벼운 눈이 내려요", 32 | "601":"❄ 눈이 내려요", 33 | "602":"❄ 강한 눈이 내려요", 34 | "611":"🌧 진눈깨비가 내려요", 35 | "612":"🌧 소나기 진눈깨비가 내려요", 36 | "615":"🌧 약한 비와 눈이 내려요", 37 | "616":"🌧 비와 눈이 내려요", 38 | "620":"🌧 약한 소나기 눈이 내려요", 39 | "621":"🌧 소나기 눈이 내려요", 40 | "622":"❄ 강한 소나기 눈이 내려요", 41 | "701":"박무", 42 | "711":"연기가 있어요", 43 | "721":"⛅ 연무", 44 | "731":"모래 먼지가 날려요", 45 | "741":"안개가 있어요", 46 | "751":"모래가 날려요", 47 | "761":"먼지가 있어요", 48 | "762":"화산재 날려요", 49 | "771":"돌풍이 있어요", 50 | "781":"토네이도", 51 | "800":"☀ 구름 한 점 없는 맑은 하늘입니다.", 52 | "801":"☁ 약간의 구름이 낀 하늘입니다.", 53 | "802":"☁ 드문드문 구름이 낀 하늘입니다.", 54 | "803":"☀ 구름이 거의 없는 하늘입니다.", 55 | "804":"☁ 구름으로 뒤덮인 흐린 하늘입니다.", 56 | "900":"토네이도", 57 | "901":"태풍", 58 | "902":"허리케인", 59 | "903":"한랭", 60 | "904":"♨ 고온", 61 | "905":"💨 바람이 있어요", 62 | "906":"우박이 떨어져요", 63 | "951":"💨 바람이 거의 없어요", 64 | "952":"💨 약한 바람이 있어요", 65 | "953":"💨 부드러운 바람이 있어요", 66 | "954":"💨 중간 세기 바람이 있어요", 67 | "955":"💨 신선한 바람이 있어요", 68 | "956":"💨 센 바람이 있어요", 69 | "957":"💨 돌풍에 가까운 센 바람이 있어요", 70 | "958":"💨 돌풍이 있어요", 71 | "959":"💨 심각한 돌풍이 있어요", 72 | "960":"🌪 폭풍이 발생했어요.", 73 | "961":"🌪 강한 폭풍이 발생했어요.", 74 | "962":"🌪 허리케인" 75 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | # WEBHOOKS list file 106 | WEBHOOKS_LIST. -------------------------------------------------------------------------------- /dist/parser/data/weather.json: -------------------------------------------------------------------------------- 1 | { 2 | "200": "🌧 가벼운 비를 동반한 천둥구름", 3 | "201": "🌧 비를 동반한 천둥구름", 4 | "202": "🌩 폭우를 동반한 천둥구름", 5 | "210": "🌩 약한 천둥구름", 6 | "211": "🌩 천둥구름", 7 | "212": "🌩 강한 천둥구름", 8 | "221": "🌩 불규칙적 천둥구름", 9 | "230": "🌩 약한 연무를 동반한 천둥구름", 10 | "231": "🌩 연무를 동반한 천둥구름", 11 | "232": "🌧 강한 안개비를 동반한 천둥구름", 12 | "300": "🌧 가벼운 안개비가 내려요", 13 | "301": "🌧 안개비가 내려요", 14 | "302": "🌧 강한 안개비가 내려요", 15 | "310": "🌧 가벼운 적은비가 내려요", 16 | "311": "🌧 적은비가 내려요", 17 | "312": "🌧 강한 적은비가 내려요", 18 | "313": "🌧 소나기와 안개비", 19 | "314": "🌧 강한 소나기와 안개비", 20 | "321": "🌧 소나기가 내려요", 21 | "500": "🌧 약한 비가 내려요", 22 | "501": "🌧 중간 비가 내려요", 23 | "502": "🌧 강한 비가 내려요", 24 | "503": "🌧 매우 강한 비가 내려요", 25 | "504": "🌧 극심한 비가 내려요", 26 | "511": "🌧 우박이 떨어져요", 27 | "520": "🌧 약한 소나기 비가 내려요", 28 | "521": "🌧 소나기 비가 내려요", 29 | "522": "🌧 강한 소나기 비가 내려요", 30 | "531": "🌧 불규칙적 소나기 비가 내려요", 31 | "600": "❄ 가벼운 눈이 내려요", 32 | "601": "❄ 눈이 내려요", 33 | "602": "❄ 강한 눈이 내려요", 34 | "611": "🌧 진눈깨비가 내려요", 35 | "612": "🌧 소나기 진눈깨비가 내려요", 36 | "615": "🌧 약한 비와 눈이 내려요", 37 | "616": "🌧 비와 눈이 내려요", 38 | "620": "🌧 약한 소나기 눈이 내려요", 39 | "621": "🌧 소나기 눈이 내려요", 40 | "622": "❄ 강한 소나기 눈이 내려요", 41 | "701": "박무", 42 | "711": "연기가 있어요", 43 | "721": "⛅ 연무", 44 | "731": "모래 먼지가 날려요", 45 | "741": "안개가 있어요", 46 | "751": "모래가 날려요", 47 | "761": "먼지가 있어요", 48 | "762": "화산재 날려요", 49 | "771": "돌풍이 있어요", 50 | "781": "토네이도", 51 | "800": "☀ 구름 한 점 없는 맑은 하늘입니다.", 52 | "801": "☁ 약간의 구름이 낀 하늘입니다.", 53 | "802": "☁ 드문드문 구름이 낀 하늘입니다.", 54 | "803": "☀ 구름이 거의 없는 하늘입니다.", 55 | "804": "☁ 구름으로 뒤덮인 흐린 하늘입니다.", 56 | "900": "토네이도", 57 | "901": "태풍", 58 | "902": "허리케인", 59 | "903": "한랭", 60 | "904": "♨ 고온", 61 | "905": "💨 바람이 있어요", 62 | "906": "우박이 떨어져요", 63 | "951": "💨 바람이 거의 없어요", 64 | "952": "💨 약한 바람이 있어요", 65 | "953": "💨 부드러운 바람이 있어요", 66 | "954": "💨 중간 세기 바람이 있어요", 67 | "955": "💨 신선한 바람이 있어요", 68 | "956": "💨 센 바람이 있어요", 69 | "957": "💨 돌풍에 가까운 센 바람이 있어요", 70 | "958": "💨 돌풍이 있어요", 71 | "959": "💨 심각한 돌풍이 있어요", 72 | "960": "🌪 폭풍이 발생했어요.", 73 | "961": "🌪 강한 폭풍이 발생했어요.", 74 | "962": "🌪 허리케인" 75 | } 76 | -------------------------------------------------------------------------------- /dist/parser/weather.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var axios_1 = __importDefault(require("axios")); 43 | var weather_json_1 = __importDefault(require("./data/weather.json")); 44 | exports.parse = function () { return __awaiter(void 0, void 0, void 0, function () { 45 | var token, city, response, data; 46 | return __generator(this, function (_a) { 47 | switch (_a.label) { 48 | case 0: 49 | token = process.env.WEATHER_API_KEY; 50 | city = 'Busan'; 51 | return [4 /*yield*/, axios_1.default.get("http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=" + token + "&units=metric")]; 52 | case 1: 53 | response = _a.sent(); 54 | data = response.data; 55 | console.log('✅ 날씨 파싱 완료'); 56 | return [2 /*return*/, { 57 | weather: weather_json_1.default[data.weather[0].id], 58 | temp: "(" + data.main.temp_min + "\uB3C4 ~ " + data.main.temp_max + "\uB3C4)" 59 | }]; 60 | } 61 | }); 62 | }); }; 63 | -------------------------------------------------------------------------------- /dist/parser/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importStar = (this && this.__importStar) || function (mod) { 39 | if (mod && mod.__esModule) return mod; 40 | var result = {}; 41 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 42 | result["default"] = mod; 43 | return result; 44 | }; 45 | Object.defineProperty(exports, "__esModule", { value: true }); 46 | var weather = __importStar(require("./weather")); 47 | var news = __importStar(require("./news")); 48 | var date = __importStar(require("./date")); 49 | var parser = function () { return __awaiter(void 0, void 0, void 0, function () { 50 | var weatherContent, newsContent, dateContent; 51 | return __generator(this, function (_a) { 52 | switch (_a.label) { 53 | case 0: return [4 /*yield*/, weather.parse()]; 54 | case 1: 55 | weatherContent = _a.sent(); 56 | return [4 /*yield*/, news.parse()]; 57 | case 2: 58 | newsContent = _a.sent(); 59 | dateContent = date.parser(); 60 | return [2 /*return*/, { 61 | weather: weatherContent, 62 | news: newsContent, 63 | date: dateContent, 64 | }]; 65 | } 66 | }); 67 | }); }; 68 | exports.default = parser; 69 | -------------------------------------------------------------------------------- /dist/parser/news.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var axios_1 = __importDefault(require("axios")); 43 | var cheerio_1 = __importDefault(require("cheerio")); 44 | exports.parse = function () { return __awaiter(void 0, void 0, void 0, function () { 45 | var response, html, $, titles, links, discordContent, slackContent, i; 46 | return __generator(this, function (_a) { 47 | switch (_a.label) { 48 | case 0: return [4 /*yield*/, axios_1.default.get('https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko')]; 49 | case 1: 50 | response = _a.sent(); 51 | html = response.data; 52 | $ = cheerio_1.default.load(html, { xmlMode: true }); 53 | titles = $('item > title').map(function (i, element) { return $(element).text(); }).get(); 54 | links = $('item > link').map(function (i, element) { return $(element).text(); }).get(); 55 | discordContent = ''; 56 | slackContent = ''; 57 | for (i = 0; i < 3; i++) { 58 | discordContent += "[" + titles[i] + "](" + links[i] + ")\n"; 59 | slackContent += "<" + links[i] + "|" + titles[i] + ">\n"; 60 | } 61 | console.log('✅ 뉴스 파싱 완료'); 62 | return [2 /*return*/, { 63 | discordContent: discordContent, 64 | slackContent: slackContent, 65 | }]; 66 | } 67 | }); 68 | }); }; 69 | -------------------------------------------------------------------------------- /dist/lib/discord.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var axios_1 = __importDefault(require("axios")); 43 | exports.default = (function (_a) { 44 | var weather = _a.weather, news = _a.news, date = _a.date, url = _a.url; 45 | return __awaiter(void 0, void 0, void 0, function () { 46 | var today, message; 47 | return __generator(this, function (_b) { 48 | switch (_b.label) { 49 | case 0: 50 | today = new Date().toLocaleDateString().replace(/\. /g, '-').replace('.', ''); 51 | message = { 52 | username: '편지봇', 53 | avatar_url: 'https://cdn.discordapp.com/attachments/683175932873539589/689459371151065088/message-3592640_1280.jpg', 54 | content: "\uD83D\uDCE8 " + today + " \uD3B8\uC9C0\uAC00 \uC654\uC5B4\uC694!", 55 | embeds: [], 56 | }; 57 | message.embeds.push({ 58 | fields: [ 59 | { 60 | name: '📅 날짜 / 한국', 61 | value: today + " " + (date ? '(' + date + ')' : ''), 62 | inline: true 63 | }, 64 | { 65 | name: '🏞️ 날씨 / 부산', 66 | value: weather.weather, 67 | inline: true 68 | }, 69 | { 70 | name: '🌡 온도 / 부산', 71 | value: weather.temp, 72 | inline: true 73 | } 74 | ], 75 | footer: { 76 | text: '제작자 : 재웜', 77 | icon_url: 'https://images-ext-2.discordapp.net/external/GyQicPLz_zQO15bOMtiGTtC4Kud7JjQbs1Ecuz7RrtU/https/cdn.discordapp.com/embed/avatars/1.png' 78 | }, 79 | }); 80 | message.embeds.push({ 81 | title: '📰 뉴스 / 구글', 82 | description: news 83 | }); 84 | return [4 /*yield*/, axios_1.default.post(url, message)]; 85 | case 1: 86 | _b.sent(); 87 | return [2 /*return*/]; 88 | } 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /dist/lib/slack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var axios_1 = __importDefault(require("axios")); 43 | exports.default = (function (_a) { 44 | var weather = _a.weather, news = _a.news, date = _a.date, url = _a.url; 45 | return __awaiter(void 0, void 0, void 0, function () { 46 | var today, message; 47 | return __generator(this, function (_b) { 48 | switch (_b.label) { 49 | case 0: 50 | today = new Date().toLocaleDateString().replace(/\. /g, '-').replace('.', ''); 51 | message = { 52 | attachments: [], 53 | }; 54 | message.attachments.push({ 55 | color: '#928BFF', 56 | pretext: "\uD83D\uDCE8 " + today + " \uD3B8\uC9C0\uAC00 \uC654\uC5B4\uC694!", 57 | fields: [ 58 | { 59 | title: '📅 날짜 / 한국', 60 | value: today + " " + (date ? '(' + date + ')' : ''), 61 | short: true, 62 | }, 63 | { 64 | title: '🏞️ 날씨 / 부산', 65 | value: weather.weather, 66 | short: true, 67 | }, 68 | { 69 | title: '🌡 온도 / 부산', 70 | value: weather.temp, 71 | short: true, 72 | }, 73 | ], 74 | footer: '제작: 재웜', 75 | footer_icon: 'https://images-ext-2.discordapp.net/external/GyQicPLz_zQO15bOMtiGTtC4Kud7JjQbs1Ecuz7RrtU/https/cdn.discordapp.com/embed/avatars/1.png', 76 | }); 77 | message.attachments.push({ 78 | // text: '', 79 | fields: [ 80 | { 81 | type: 'mrkdwn', 82 | title: '📰 뉴스 / 구글', 83 | value: news, 84 | } 85 | ], 86 | }); 87 | return [4 /*yield*/, axios_1.default.post(url, message)]; 88 | case 1: 89 | _b.sent(); 90 | return [2 /*return*/]; 91 | } 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /dist/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importStar = (this && this.__importStar) || function (mod) { 39 | if (mod && mod.__esModule) return mod; 40 | var result = {}; 41 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 42 | result["default"] = mod; 43 | return result; 44 | }; 45 | var __importDefault = (this && this.__importDefault) || function (mod) { 46 | return (mod && mod.__esModule) ? mod : { "default": mod }; 47 | }; 48 | Object.defineProperty(exports, "__esModule", { value: true }); 49 | var core = __importStar(require("@actions/core")); 50 | var parser_1 = __importDefault(require("./parser")); 51 | var discord_1 = __importDefault(require("./lib/discord")); 52 | var slack_1 = __importDefault(require("./lib/slack")); 53 | (function () { return __awaiter(void 0, void 0, void 0, function () { 54 | var WEBHOOKS, webhookList, parsed; 55 | return __generator(this, function (_a) { 56 | switch (_a.label) { 57 | case 0: 58 | WEBHOOKS = process.env.WEBHOOKS; 59 | if (WEBHOOKS == null) 60 | throw new Error('웹훅 리스트를 찾을 수 없어요.'); 61 | webhookList = WEBHOOKS.split(','); 62 | return [4 /*yield*/, parser_1.default()]; 63 | case 1: 64 | parsed = _a.sent(); 65 | webhookList.map(function (url) { return __awaiter(void 0, void 0, void 0, function () { 66 | return __generator(this, function (_a) { 67 | switch (_a.label) { 68 | case 0: 69 | if (!url.includes('discordapp.com')) return [3 /*break*/, 2]; 70 | return [4 /*yield*/, discord_1.default({ 71 | weather: parsed.weather, 72 | news: parsed.news.discordContent, 73 | date: parsed.date, 74 | url: url, 75 | })]; 76 | case 1: 77 | _a.sent(); 78 | return [3 /*break*/, 4]; 79 | case 2: 80 | if (!url.includes('hooks.slack.com')) return [3 /*break*/, 4]; 81 | return [4 /*yield*/, slack_1.default({ 82 | weather: parsed.weather, 83 | news: parsed.news.slackContent, 84 | date: parsed.date, 85 | url: url, 86 | })]; 87 | case 3: 88 | _a.sent(); 89 | _a.label = 4; 90 | case 4: return [2 /*return*/]; 91 | } 92 | }); 93 | }); }); 94 | console.log('✅ 웹훅 발송 완료'); 95 | return [2 /*return*/]; 96 | } 97 | }); 98 | }); })().catch(function (e) { 99 | console.error(e); 100 | core.setFailed(e); 101 | }); 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 65 | 66 | "resolveJsonModule": true 67 | } 68 | } 69 | --------------------------------------------------------------------------------