├── .gitignore ├── 0화 ├── README.MD ├── node_js.md └── vsc.md ├── 1화 ├── README.MD └── bot.js ├── 2화 ├── README.MD ├── bot.js ├── docs.md └── webhook.MD ├── 3화 ├── Commands │ ├── Bot │ │ ├── embed.js │ │ ├── ping.js │ │ └── webhook.js │ └── moderator │ │ ├── ban.js │ │ ├── clear.js │ │ └── kick.js ├── README.MD ├── bot.js ├── example.config.json └── module_install.md ├── 4화 ├── Commands │ ├── Bot │ │ ├── embed.js │ │ ├── ping.js │ │ └── webhook.js │ ├── crawling │ │ └── docs.js │ └── moderator │ │ ├── ban.js │ │ ├── clear.js │ │ └── kick.js ├── README.MD ├── bot.js └── example.config.json ├── 5화 ├── Commands │ ├── Bot │ │ ├── embed.js │ │ ├── help.js │ │ ├── ping.js │ │ └── webhook.js │ ├── Owner │ │ └── eval.js │ ├── crawling │ │ ├── NaverRanking.js │ │ └── docs.js │ └── moderator │ │ ├── ban.js │ │ ├── clear.js │ │ └── kick.js ├── README.MD ├── bot.js └── example.config.json ├── 6화 ├── Commands │ ├── Bot │ │ ├── embed.js │ │ ├── help.js │ │ ├── ping.js │ │ └── webhook.js │ ├── Owner │ │ └── eval.js │ ├── crawling │ │ ├── NaverRanking.js │ │ └── docs.js │ ├── database │ │ ├── gamble.js │ │ ├── money.js │ │ ├── registerGuild.js │ │ └── settings.js │ └── moderator │ │ ├── ban.js │ │ ├── clear.js │ │ └── kick.js ├── README.MD ├── bot.js ├── example.config.json └── json │ ├── server.json │ └── user.json ├── LICENSE ├── README.md └── 읽어보면 좋은것들 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | /log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | node_modules 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | *.txt 108 | 109 | package-lock.json 110 | package.json 111 | config.json 112 | -------------------------------------------------------------------------------- /0화/README.MD: -------------------------------------------------------------------------------- 1 | ### 이번 튜토리얼은 초보분들을 대상으로 제작하였습니다! 2 | 3 | 0화는 node.js 설치하는 방법과 vsc(Visual Studio Code) 설치 방법에 대한 설명을 적을 예정입니다. 4 | 잘 따라오셔서 성공하셨으면 좋겠어요!! 5 | 6 | Node_js.md 와 VSC.md로 나누었으니 각각 확인해주세요. -------------------------------------------------------------------------------- /0화/node_js.md: -------------------------------------------------------------------------------- 1 | ### Node.js 설치 하는 방법 2 | 3 | 1. Node.js 공식 사이트를 들어가주세요. [여기를 눌러주세요!](https://nodejs.org/en/) 4 | 5 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711347014419611709/unknown.png) 6 | 7 | 2. 여기서 12.16.3 LTS 버전을 클릭하여 다운 받아주세요. 8 | > **LTS버전**은 장기적으로 node.js 팀에서 지원해주는 버전이기에 사용합니다. 9 | 10 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711347272335491263/unknown.png) 11 | 12 | *혹시나 윈도우 이외에 다른 운영체제를 사용하신다면 아래에 Other Downloads를 눌러서 자신에게 맞는 운영체제를 다운받아주세요.* 13 | 14 | 3. 다운을 다 하셨다면 실행 해주세요. 15 | 16 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711348054627713135/unknown.png) 17 | 18 | 4. 계속 next 누르시고 설치하시면 됩니다! 19 | 20 | 5. 그러고 설치를 다 하시고 finish 버튼을 누르면 끝! 21 | -------------------------------------------------------------------------------- /0화/vsc.md: -------------------------------------------------------------------------------- 1 | ### VSC (Visual Studio Code) 설치 하는 방법 2 | 3 | 1. VSC 공식 사이트를 들어가주세요. [여기를 눌러주세요!](https://code.visualstudio.com/) 4 | 5 | 2. 들어가셨다면 다운로드 버튼을 눌러 다운해주세요. 6 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711352218959216750/unknown.png) 7 | 8 | 3. 누르셨으면 아래의 이미지처럼 나오셨을텐데 맞는 OS를 찾아 다운로드 해주세요. 9 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711352447720751135/unknown.png) 10 | 11 | 4. 다 다운로드 하시고 실행하시면 아래의 사진처럼 보일꺼에요! 12 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711367804632432670/unknown.png) 13 | 14 | 계약을 동의해주시고 다음 버튼을 계속 누르고 설치하시면 끄읕! 15 | 16 | 5. VSC 한글 버전으로 바꾸는 방법을 알려드릴꺼에요! 17 | 1) 왼쪽에 여러개의 아이콘이 있는데 그중에 아래것을 눌러주세요! 18 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711368629236662322/unknown.png) 19 | 20 | 2) 거기에 검색창이 있을텐데 **Korean Language Pack for Visual Studio Code**라고 적어주세요. 21 | 3) 거기서 맨 첫번째줄에 Korean Language 머시기 있을꺼에요 그거 install 해주세요. 22 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711369090610102342/unknown.png) 23 | 4) 그러고 오른쪽 아래에 밑에 사진처럼 보이실 꺼에요 **Restart Now**를 눌러주세요! 24 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711369267391758346/unknown.png) 25 | 5) 짜잔 그러면 한글로 바뀐것을 확인하실 수 있어요! 26 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711369517674135582/unknown.png) 27 | -------------------------------------------------------------------------------- /1화/README.MD: -------------------------------------------------------------------------------- 1 | ## 먼저 [여기](https://discord.com/developers/applications)를 클릭하여 웹사이트에 들어가주세요. 2 | 3 | ![들어가시면 아래의 사진처럼 보이실겁니다.](https://cdn.discordapp.com/attachments/708325535133990963/719143421083779092/unknown.png) 4 | 5 | 여기서 오른쪽 위에 보시면 New Application을 클릭해주세요. 6 | 7 | ![그러면 아래의 그림처럼 보이실겁니다.](https://cdn.discordapp.com/attachments/708325535133990963/708326886845186099/unknown.png) 8 | 9 | 자신이 원하는 이름을 적어주시고 Create 버튼을 눌러주세요. 10 | (팀은 Discord Developer Teams에 어느 팀의 소속이라면 뜨는 라인입니다.) 11 | 12 | 저는 Discord JS Tutorial로 이름을 작성하였습니다. 13 | 14 | ![완료를 하셨다면 아래의 사진처럼 보이실겁니다.](https://cdn.discordapp.com/attachments/708325535133990963/708327210758701098/unknown.png) 15 | 16 | 여기서 왼쪽에 Settings 보시면 Bot라고 있는데 클릭해주세요. 17 | 18 | ![](https://cdn.discordapp.com/attachments/708325535133990963/708327595539955752/unknown.png) 19 | 20 | 그리고 여기서 Add Bot을 누르고 Yes, do it! 버튼을 눌러주세요. 21 | 22 | 그러면 Bot 라인에 아래의 사진처럼 보이실겁니다! 23 | ![](https://cdn.discordapp.com/attachments/708325535133990963/708328919979261992/unknown.png) 24 | 25 | 여기서 봇의 아이콘이나 유저닉네임을 수정할 수 있습니다. 26 | 27 | 우리가 사용할 것은 토큰이니 토큰라인에 Copy를 눌러주시고 봇 소스에 "토큰" 이라고 적혀있는 곳을 지워주시고 붙여넣기 해주세요. 28 | 29 | 30 | # **※ 주의사항 ※** 31 | 1. **Regenerate**를 눌러주시면 봇 토큰이 바뀌고 로그인 되어있던 모든 수단들은 연결이 끊깁니다. 32 | 2. 토큰은 **자신만이 알아야 하고 절때로 누군가에게 도움을 받기 위해서 소스코드를 전체 복사하여 보낼 때 각별히 주의**를 해주셔야 합니다. 33 | 34 | ### Discord.js 모듈 설치하는 방법 35 | 36 | 1. 코딩을 할 폴더를 하나 생성하시고 그 폴더에 들어가주세요. 37 | 2. 그 폴더에서 경로 부분에 클릭하시고 cmd라고 적으신 후 엔터를 쳐주세요! 38 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711371559712981043/unknown.png) 39 | 3. 엔터를 치시고 cmd창이 하나 생성되었을겁니다. 거기서 **npm i discord.js --save** 라고 적으신 후 엔터를 쳐주세요. 40 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711371759273771058/unknown.png) 41 | 4. 그러고 설치가 다 완료되었으면 아래의 사진처럼 되었을겁니다! 42 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711371987900956713/unknown.png) 43 | 44 | 45 | 46 | ### node package 생성하는 방법 47 | 48 | 1. 위에서 작업했던 cmd 창을에서 **npm init**를 해주세요. 49 | 2. 거기서 이제 여러가지 머 적으라고 할텐데 양식에 맞게 적어주세요! 50 | 3. 그러면 아래의 사진처럼 되어있을텐데 거기서 엔터를 눌러서 마무리 해주세요! 51 | 52 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/719143887955951616/unknown.png) 53 | 우리는 그러면 Discord.js 라는 모듈을 정상적으로 설치한 것을 알 수 있습니다! 54 | 나머지는 봇 파일에서 뵙도록 합시다! 55 | 56 | #### Made By [Jasper](https://github.com/Ukong0324) -------------------------------------------------------------------------------- /1화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); // Node Module Package 에서 우리가 사용할 Discord.js 모듈을 불러옵시다. 2 | const client = new Discord.Client(); // Discord.js 에서 핵심적으로 쓰이는 아이는 Client 라는 친구를 사용하게 됩니다. 3 | // client 라는 친구를 bot으로 수정하여 사용해도 되긴 해요! 하지만 이후에 작성되는 모든 코드에 client가 들어갈 예정이니 참고해주세요! 4 | 5 | client.on("ready", () => { // client에 이벤트 함수인 ready를 사용하였습니다. 6 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); // console.log는 콘솔에 메세지를 보내는 의미에요, 우리가 보낼 메세지는 "USER#1234 봇에 로그인 하였습니다." 라고 보낼겁니다. 7 | }); // 세미콜론(;)은 사용해도 상관 없어요 하지만 세미콜론 뒤에는 아무것도 사용하실 수 없으니 참고해주세요! 8 | // 그리고 괄호 잘 닫았나 잘 확인하면서 코드를 작성해야해요! 안그러면 에러를 뿜게 되거든요 ㅡㅅㅡ... 9 | // Tip! on과 once는 차이가 큽니다! "계속 들을 것이냐" 아니면 "한번만 들을 것이냐" 의 큰 차이니 조심하여 사용해주세요. 10 | client.on("message", msg => { // client에 이벤트 함수인 Message를 사용하였습니다. 11 | if (msg.content === "핑") { // 만약에 message를 사용하는 곳의 내용이 "핑" 이라고 적었다면 12 | msg.reply("퐁!"); // 우리는 "퐁!" 이라고 답변을 해줍시다! 13 | } 14 | }); 15 | 16 | client.login("토큰"); // Client를 로그인 하기 위해서는 토큰이라는 것이 필요해요! 17 | /** 18 | * 토큰은 자신만이 알아야 하고 절때로 누군가에게 도움을 받기 위해서 소스코드를 전체 복사하여 보낼 때 각별히 주의를 해주셔야 합니다. 19 | * 1. https://discord.com/developers/applications 사이트를 들어가주세요. 20 | * 2. 자신이 만들려 한 봇의 애플리케이션을 클릭하여 접속해주세요. 21 | * 3. SETTINGS 라인에 BOT이라는 곳을 들어가주세요. 22 | * 4. Build-A-Bot에 Add Bot이라고 있습니다 클릭하여 봇을 추가해주세요. 23 | * 5. Token이라는 곳에서 Copy 버튼을 눌러주세요 24 | * 6. 해당 토큰을 로그인 할려는 공간에 붙어 넣어주시면 됩니다. 25 | * 7. 해당 토큰은 유포하시면 "위험"하오니 조심하여 다뤄주시길 바랍니다. 26 | */ -------------------------------------------------------------------------------- /2화/README.MD: -------------------------------------------------------------------------------- 1 | ## 개발하시기 전에 잠깐 읽어주세요! 2 | 3 | 1. 1화에서 알려드리지 못한 도큐들을 아래에 설명해놓을테니 확인해주시고 개발해주세요. 4 | 영문(공식): 5 | > 업데이트 될 때마다 최신으로 업데이트 되오니 영어를 혐오하지 않으시면 최대한 공식링크를 이용해주시면 좋을거 같습니다. 6 | 7 | 한글(비공식, 번역진행중): 8 | > [Discord.js Korea](https://github.com/discordjs-kr) 팀에서 번역하고 있는 비공식 라이브러리 입니다. 9 | 10 | 2. 현재 튜토리얼은 초보분들을 대상으로 작성되고 있는 소스입니다. 11 | 너무 쉽다고 혼자서 자만하지 말아주세요.. 12 | 코드 설명중에 미숙한 점이나 버그가 발견되면 **Issues, Pull Requests**를 넣어주시면 감사합니다. 13 | 14 | 3. 오류는 [Google](https://www.google.co.kr/?gws_rd=ssl) 에서 검색하시면 왠만한 에러에 대한 피드백이 적혀있습니다. 15 | 대표적인 오류에 대한 설명들을 미리 적어놓을게요. 16 | ```js 17 | 1. "Error: Cannot find module" 18 | -> 해당 에러는 모듈이 설치되어 있지 않거나 해당 디렉토리에 없는 경우를 말합니다. 19 | 2. "ReferenceError: Content is not defined" 20 | -> 해당 에러는 코드를 작성하실 때에 선언되지 않은 구문을 작성하여 나오는 문제이니 수정해주시면 됩니다. 21 | 3. "TypeError: Cannot read property 'id' of null" 22 | -> 만약에 test.id 라는 것이 있는데 id의 값이 null 이면 나오는 에러입니다. 23 | ``` 24 | 25 | 4. 웹훅에 대한 설명은 **webhook.MD**에 적혀있습니다. 26 | **웹훅(WebHook)에 대해서 잘 모르시는 분들은 꼭 읽어주세요.** 27 | 28 | 5. Discord.js 라이브러리 읽는 방법은 **docs.md**에 적혀있습니다. 29 | **라이브러리를 읽으실 줄 모르시는 분들은 꼭 읽어주세요..** -------------------------------------------------------------------------------- /2화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const client = new Discord.Client(); 3 | const prefix = "튜토야 " // 우리는 이제 접두사를 추가할꺼에요! (저는 튜토야 라고 했습니다!) 접두사가 붙어야 커맨드가 실행이 될 수 있습니다! 4 | 5 | client.on("ready", () => { 6 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); 7 | }); 8 | client.on("message", msg => { 9 | if (!msg.guild) return; // 만약에 길드 이외 다른곳이라면 return을 사용을 해줍시다. 아래도 마찬가지이지만 return을 해주어 반응을 해주지 않는 반응을 해주는 것이에요! 10 | if (msg.author.bot) return; // 여기도 마찬가지로 만약에 메세지 사용자가 봇이라면 return을 사용합시다. 11 | if (msg.content.indexOf(prefix) !== 0) return; //메세지가 prefix로 시작되지 않을시 return을 사용을 해줍시다 12 | var args = msg.content.slice(prefix.length).trim().split(/ +/g); // argument(args) 이 부분은 args를 원하는 방식으로 만들기 위한 과정이라고만 아시면 됩니다! 13 | var command = args.shift().toLowerCase(); //명령어를 가져올꺼에요 args의 어레이중 가장 앞부분을 가져옵니다 toLowerCase()는 대문자를 소문자로 변경시켜줍니다. Kick같은 실수를 방지할수 있죠 14 | if (command === `핑`) { // 이전에 핑을 퐁으로 답변했다면 웹소켓 지연시간을 알려주는 코드로 해봅시다! 15 | msg.reply(`${client.ws.ping}ms`); // CLIENT에 WS(WEBSOCKET)이라는 곳에서 PING을 구해오는 값입니다. 16 | } 17 | if (command === `임베드`) { 18 | var embed = new Discord.MessageEmbed() 19 | .setTitle("여기는 대표 타이틀!") // 여기는 임베드에서 타이틀로 사용됩니다! 20 | .setDescription("여기는 대표 설명!") // 여기는 타이틀을 설명해주는 걸로 사용됩니다! 21 | .setColor("RED") // 여기는 색상을 설정하는 공간인데 HEX값을 넣으셔도 됩니다! (#7239DA) "RED" 말고 다른것들도 있어요! 맨 밑에다가 적어놓을테니 확인해주세요! 22 | .setFooter("여기는 말머리?") // 여기는 임베드의 밑부분에서 말머리로 사용됩니다! 23 | .setThumbnail("http://blogfiles.naver.net/20151023_23/shin_0305_1445573936921jrPRT_JPEG/%BD%E6%B3%D7%C0%CF%BF%B9%BD%C3.jpg") // 여기는 임베드에서 썸네일로 불려옵니다! (URL를 넣어가 경로를 기입하면 그 경로에 있는 이미지를 불러와 썸네일로 이용되요!) 24 | .setImage("http://blogfiles.naver.net/MjAxODA4MjNfMjQ0/MDAxNTM1MDE5ODk1Njc3.c5p_E9tLPEXGnXPAkpOuhpEOm7VLqopETMTfJ9C8CWYg.6FCsIDtjWnd19lSzmw_z1oHm9E7fd39s1RmRPeBOF3Ag.JPEG.dlawldbs20/VD-poem-20150915-01.jpg") // 여기는 임베드에서 이미지로 사용되는 곳입니다. // 위에 설명이랑 같아요 25 | 26 | .setTimestamp() // 여기는 타임스탬프를 설정하는 공간인데 비워두면 현재시각, 여기에 타임스탬프를 넣으시면 그 값에 맞는 시간으로 변환됩니다! 27 | .addField("여기는 소제목", "여기는 소설명(??)") // 첫번째 칸은 임베드의 소제목, 두번째 칸은 임베드의 소제목의 설명하는 공간입니다! 세번째 칸은 INLINE으로 사용되는데 TRUE 하면 라인에 들어가는거고 FALSE 하면 밑라인으로 내려가게 됩니다. 28 | msg.reply(embed) // EMBED를 REPLY로 답변합시다! 29 | } 30 | if (command === `웹훅`) { 31 | const hook = new Discord.WebhookClient('WebHook ID', 'WebHook Token'); // https://discordapp.com/api/webhooks/{WebHook ID}/{WebHook Token} // 웹훅 생성하는 방법은 README.MD에서 설명해드릴꺼에요! 32 | hook.send("Hello, new World!") // 웹훅이 채널이 지정되어 있는 곳에다가 우리는 "Hello, new World" 라고 보내주는거에요! 33 | } 34 | 35 | /** 36 | * Moderator (관리자 기능!) 37 | * 여기 부분은 관리자 기능을 추가할려고 해요! 38 | * 코드를 꼭 이해를 하시고 복붙만 하여 사용하여 문의를 안해주셨으면 좋겠어용.. 39 | * ※ 주의사항 ※ 40 | * 주석으로 설명하는 라인을 잘 확인하시고 403 (permissions denial) 안뜨게 봇의 권한을 잘 사용해주세요. 41 | */ 42 | 43 | if (command === `추방`) { // 만약에 메세지 내용이 추방이라면 ? 44 | var user = msg.mentions.users.first(); // var로 user를 선언을 해줍시다. (맨션을 먼저 언급을 해주라 라는 의미에용, 맨션을 안하면 undefined가 뜹니다) 45 | if (!user) { // 그래서 만약에 user가 안된다면 46 | msg.reply("추방하시기 전에 맨션을 먼저 해주세요!") // 맨션을 먼저 해달라고 문구를 전송해줍시다! 47 | } else { // 아니라면? 48 | var member = msg.guild.member(user); // var로 member를 선언을 해줍니다 49 | if (member) { // 만약에 member가 있다면 50 | member.kick(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { // 해당 유저를 추방하고, audit log에 추방을 당했다고 로그를 남겨줍시다. 51 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) // 그리고 채팅 친 곳에서 해당 유저를 추방 당했다고 알려줍시다. 52 | }) 53 | } else { // member가 없다면 54 | msg.reply("이 서버에 존재하지 않은 유저입니다!") // 서버에 없는 존재를 맨션하였다고 알려줍시다! 55 | } 56 | } 57 | } 58 | 59 | if (command === `차단`) { // 만약에 메세지 내용이 차단이라면? 60 | var user = msg.mentions.users.first(); // var로 user를 선언을 해줍시다. (맨션을 먼저 언급을 해주라 라는 의미에용, 맨션을 안하면 undefined가 뜹니다) 61 | if (!user) { // 그래서 만약에 user가 안된다면 62 | msg.reply("차단하시기 전에 맨션을 먼저 해주세요!") // 맨션을 먼저 해달라고 문구를 전송해줍시다! 63 | } else { 64 | var member = msg.guild.member(user); // var로 member를 선언을 해줍니다 65 | if (member) { // 만약에 member가 있다면 66 | member.ban(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { // 해당 유저를 추방하고, audit log에 차단을 당했다고 로그를 남겨줍시다. 67 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) // 그리고 채팅 친 곳에서 해당 유저를 차단당했다고 알려줍시다. 68 | }) 69 | } else { // member가 없다면 70 | msg.reply("이 서버에 존재하지 않은 유저입니다!") // 서버에 없는 존재를 맨션한걸로 전송해줍시다! 71 | } 72 | } 73 | } 74 | 75 | if (command === `청소`) { // 만약에 메세지 내용이 청소라면? 76 | if (!args[0]) return msg.reply("청소할 만큼의 값을 정수로 적어주세요!") // 만약에 argument가 비어있다면? 값을 적어달라고 메세지를 답변해줍시다. 77 | if (!Number(args[0])) return msg.reply("메세지를 지울 값이 숫자가 아니면 안되요!") // 만약에 argument가 숫자가 아니라면 숫자로 적어달라고 답변해줍시다. 78 | if (args[0] < 1) return msg.reply("메세지를 지울 값을 1보다 작게 하시면 안되요!") // 만약에 argument가 1보다 작으면 그렇게 못한다고 답변해줍시다. 79 | if (args[0] > 100) return msg.reply("메세지를 지울 값이 100보다 크면 메세지가 안지워져요!") // 만약에 argument가 100보다 크면 그렇게 못한다고 답변해줍시다 (최대 100개 삭제가능.) 80 | 81 | msg.channel.bulkDelete(args[0]).then(msg.reply(`성공적으로 ${args[0]}개 만큼 메세지를 삭제하였습니다!`)) // message.channel 에서 bulkDelete 라는 것을 사용하여 수 만큼 삭제한 후 then으로 "몇개를 삭제하였다"라고 답변해줍시다. 82 | } 83 | }); 84 | 85 | client.login("토큰") 86 | 87 | /** 88 | * 그리고 제가 1화에서 설명하지 못한 부분이 있었어요 ㅠㅠ 89 | * 그리고 저희가 하고 있는 과정은 Discord.JS라는 라이브러리를 사용하여 코딩을 진행하였는데, 문서를 제가 따로 안알려드렸더라고요! 90 | * 그래서 제가 아래에 링크 2개를 적어놓을테니 확인해주세요! 91 | * 영문판 (공식): https://discord.js.org/ 92 | * 한글판 (번역중, 비공식): https://discord-kr.js.org/ 93 | */ 94 | 95 | /** 96 | * Discord MessageEmbed Color List 97 | * - `DEFAULT` 98 | * - `WHITE` 99 | * - `AQUA` 100 | * - `GREEN` 101 | * - `BLUE` 102 | * - `YELLOW` 103 | * - `PURPLE` 104 | * - `LUMINOUS_VIVID_PINK` 105 | * - `GOLD` 106 | * - `ORANGE` 107 | * - `RED` 108 | * - `GREY` 109 | * - `DARKER_GREY` 110 | * - `NAVY` 111 | * - `DARK_AQUA` 112 | * - `DARK_GREEN` 113 | * - `DARK_BLUE` 114 | * - `DARK_PURPLE` 115 | * - `DARK_VIVID_PINK` 116 | * - `DARK_GOLD` 117 | * - `DARK_ORANGE` 118 | * - `DARK_RED` 119 | * - `DARK_GREY` 120 | * - `LIGHT_GREY` 121 | * - `DARK_NAVY` 122 | * - `RANDOM` 123 | */ 124 | -------------------------------------------------------------------------------- /2화/docs.md: -------------------------------------------------------------------------------- 1 | ### 라이브러리를 쉽게 읽는 방법! 2 | 3 | 1. 일단은 **라이브러리를 먼저 들어가**볼까요? 4 | [Offcial Docs](https://discord.js.org/#/) | [Unoffical Korean Docs](https://discord-kr.js.org/#/) 5 | 6 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715862892955566080/unknown.png) 7 | 8 | 2. 우리는 docs를 읽으러 왔으니 **오른쪽 위에 Documentation를 눌러**줍시다! 9 | 10 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715863273978855451/unknown.png) 11 | 12 | 3. 음.. 일단은 **검색을 할려면 오른쪽 위에 Search**라고 있어요! 거기서 한번 **Message** 라는걸 찾아봅시다! 13 | 14 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715864091381596221/unknown.png) 15 | 16 | 4. 으아... 엄청나게 결과 값이 나오죠? 17 | 일단은 제가 찾을려는 것이 **클래스 문이니 🇨 라고 되어있는 곳에 Message를 클릭**해줍시다! 18 | 19 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715865222312624129/unknown.png) 20 | 21 | 5. 그러면 위에 사진처럼 보일거에요! 22 | 23 | 그러면 질문이 있을꺼에요! 24 | 25 | Q: 제작자님 여기서 **Properties, Methods**가 무엇인가요? 26 | A: Properties: **괄호를 사용하지 않고 결과값을 표출**을 해줍니다. 27 | Methods: **괄호를 사용하여 괄호 안에 값을 기입하여 결과값을 표출**을 해줍니다. 28 | 29 | 우리가 늘 사용하게 될 **message에서 한번 content 찾아**볼까요? 30 | 31 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715865529646317628/unknown.png) 32 | 33 | 6. 클릭을 해보니 해당 라인에 자동으로 이동되면서 **설명이 적혀**있네요! 34 | The content of the message (해석하면 메세지 내용 이라고 되겠네요) 35 | 36 | 7. 그러면 우리가 **메인 파일에서 if (message.content) 를 쓰는것이 어떤 의미였는지 알게 되었어**요! 37 | 그러면... **마지막으로 하나 더** 해볼까요? 38 | 39 | - - - 40 | 41 | 8. 한번 guildMember를 찾아봅시다! 42 | 43 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/715866148029202552/unknown.png) 44 | 45 | 9. 여기서 이제 질문이 하나 올라올꺼에요. 46 | Q: 제작자님! **guildMember**가 머에요..? 47 | A: 설명을 드리자면 **우리가 흔히들 말하는 서버가 여기서는 guild(길드)**로 표현하고 있는데 48 | 그 안에서 **우리는 유저라고 표현하긴 하지면 member(멤버)**로 표현하고 있어요! 49 | 약간 코드 형식으로 말씀 드리자면 50 | 51 | ```js 52 | message.member 53 | ``` 54 | 55 | 이렇게 쓰시면 저기서 **member가 GuildMember로 표현**이 되요! 56 | 57 | 10. 나머지는 알아서 충분히 하실 수 있다고 봐요! 58 | ***정말로 모르겠으면 디스코드로 연락주세요!*** 59 | 60 | ### **라이브러리 읽는 법은 여기까지 마치도록 하겠습니다!** 61 | 62 | 나머지는 메인 파일에서 뵙도록 합시다! 63 | -------------------------------------------------------------------------------- /2화/webhook.MD: -------------------------------------------------------------------------------- 1 | ### 웹훅을 생성하는 방법! 2 | 3 | **만약에 당신이 서버에 관리자 권한이나 따로 웹훅 권한이 있으시다면 생성하거나 관리를 하실 수 있습니다!** 4 | 5 | ![관리자](https://cdn.discordapp.com/attachments/708325535133990963/709005658711654430/unknown.png) 6 | ![웹훅 관리](https://cdn.discordapp.com/attachments/708325535133990963/709005853629612053/unknown.png) 7 | 8 | #### 1. 왼쪽 위에 서버 이름 적혀있는 곳을 누르시고 **서버 관리**를 들어가주세요. 9 | 10 | ![서버 이름 적힌곳 누르면 나오는 이미지](https://cdn.discordapp.com/attachments/708325535133990963/709005304632705064/unknown.png) 11 | 12 | #### 2. 왼쪽에 여러가지 있을텐데 **웹후크**를 들어가주세요. 13 | 14 | ![웹후크](https://cdn.discordapp.com/attachments/708325535133990963/709006515390119957/unknown.png) 15 | 16 | #### 3. **웹후크 만들기**를 누르시고 편집 라인에서 **웹후크 이름을 수정하고 원하는 채널을 설정**을 해주신 후 URL를 복사해주세요.. 17 | (**"저는 webhook-test로 설정하였어요"**) 18 | 19 | ![편집라인](https://cdn.discordapp.com/attachments/708325535133990963/709007299431366736/unknown.png) 20 | 21 | #### 4. 그러면 이제 URL를 확인해봅시다. 22 | 23 | 일단 제가 만든 웹훅의 URL은 아래와 같아요! 24 | **https://discordapp.com/api/webhooks/709006707640107029/piBftlF4QTdTfZdvs6vUJGRZBtdv-GrfAC-fFNBVfncXAfFhkyyHh_xr14cBkiEhRi7j** 25 | 26 | 웹훅의 아이디: 709006707640107029 27 | 웹훅의 토큰: piBftlF4QTdTfZdvs6vUJGRZBtdv-GrfAC-fFNBVfncXAfFhkyyHh_xr14cBkiEhRi7j 28 | 29 | 이런식으로 되어있을꺼에요! 30 | 이것들을 이제 어떻게 봇 소스에 적용하나? 31 | 32 | ```js 33 | const hook = new Discord.WebhookClient('709006707640107029', 'piBftlF4QTdTfZdvs6vUJGRZBtdv-GrfAC-fFNBVfncXAfFhkyyHh_xr14cBkiEhRi7j') 34 | 35 | hook.send("Hello, new World") 36 | ``` 37 | 이런식으로 사용할 수 있어요! 38 | -------------------------------------------------------------------------------- /3화/Commands/Bot/embed.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | let embed = new Discord.MessageEmbed() // var -> let으로 수정하였습니다. 4 | .setTitle("여기는 대표 타이틀!") 5 | .setDescription("여기는 대표 설명!") 6 | .setColor("RED") 7 | .setFooter("여기는 말머리?") 8 | .setThumbnail("http://blogfiles.naver.net/20151023_23/shin_0305_1445573936921jrPRT_JPEG/%BD%E6%B3%D7%C0%CF%BF%B9%BD%C3.jpg") 9 | .setImage("http://blogfiles.naver.net/MjAxODA4MjNfMjQ0/MDAxNTM1MDE5ODk1Njc3.c5p_E9tLPEXGnXPAkpOuhpEOm7VLqopETMTfJ9C8CWYg.6FCsIDtjWnd19lSzmw_z1oHm9E7fd39s1RmRPeBOF3Ag.JPEG.dlawldbs20/VD-poem-20150915-01.jpg") 10 | .setTimestamp() 11 | .addField("여기는 소제목", "여기는 소설명(??)") 12 | msg.reply(embed) 13 | } 14 | 15 | exports.config = { 16 | name: '임베드', 17 | aliases: ['embed', 'dlaqpem'], 18 | category: ['bot'], 19 | des: ['임베드에 대한 설명'], 20 | use: ['튜토야 임베드'] 21 | } -------------------------------------------------------------------------------- /3화/Commands/Bot/ping.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | msg.channel.send(`${client.user.username}의 핑은 ${client.ws.ping}ms 입니다!`) 3 | } 4 | 5 | exports.config = { 6 | name: '핑', 7 | aliases: ['vld', 'botping'], 8 | category: ['bot'], 9 | des: ['봇의 디스코드 웹소켓 지연시간을 알려드립니다'], 10 | use: ['튜토야 핑'] 11 | } -------------------------------------------------------------------------------- /3화/Commands/Bot/webhook.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | const hook = new Discord.WebhookClient('WebHook ID', 'WebHook Token'); 4 | hook.send("Hello, new World!") 5 | } 6 | 7 | exports.config = { 8 | name: '웹훅', 9 | aliases: ['vld', 'botping'], 10 | category: ['bot'], 11 | des: ['웹훅에 대한 사용방법'], 12 | use: ['튜토야 웹훅'] 13 | } -------------------------------------------------------------------------------- /3화/Commands/moderator/ban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("차단하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.ban(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 차단 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '차단', 19 | aliases: ['ban', '벤'], 20 | category: ['moderator'], 21 | des: ['유저를 해당 서버에서 차단 시킵니다.'], 22 | use: ['튜토야 차단 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /3화/Commands/moderator/clear.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | if (!args[0]) return msg.reply("청소할 만큼의 값을 정수로 적어주세요!") 3 | if (!Number(args[0])) return msg.reply("메세지를 지울 값이 숫자가 아니면 안되요!") 4 | if (args[0] < 1) return msg.reply("메세지를 지울 값을 1보다 작게 하시면 안되요!") 5 | if (args[0] > 100) return msg.reply("메세지를 지울 값이 100보다 크면 메세지가 안지워져요!") 6 | 7 | msg.channel.bulkDelete(args[0]).then(msg.reply(`성공적으로 ${args[0]}개 만큼 메세지를 삭제하였습니다!`)) 8 | } 9 | 10 | exports.config = { 11 | name: '청소', 12 | aliases: ['clear', 'clean'], 13 | category: ['moderator'], 14 | des: ['bulkdelete'], 15 | use: ['튜토야 청소 <청소 할 메세지의 수>'] 16 | } -------------------------------------------------------------------------------- /3화/Commands/moderator/kick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("추방하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.kick(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 킥 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '추방', 19 | aliases: ['킥', 'kick'], 20 | category: ['moderator'], 21 | des: ['유저를 강제퇴장 시킵니다.'], 22 | use: ['튜토야 추방 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /3화/README.MD: -------------------------------------------------------------------------------- 1 | ### 잠시 튜토리얼을 보시기 전 읽어주세요. 2 | 3 | 이번 3화 튜토리얼은 커맨드 핸들링에 관련하여 제작한 겁니다. 4 | 소스 코드를 사용하시는 것은 좋으시나 꼭 소스를 이해하셨으면 바램을 가져봅니다... 5 | 봇 소스만 복붙하여 사용하는 행위는 절대로 좋은 행위가 아닙니다. 6 | 튜토리얼을 제작하는 목적은 봇을 입문하시는 분들을 위하여 공부하시라고 제작하는거지 복붙 하라고 만든 용도가 아닙니다.. 7 | 8 | 9 | ### Docs (라이브러리) 10 | 11 | 영문(공식): 12 | > 업데이트 될 때마다 버전에 맞게 업데이트되니 영어를 혐오하지 않으시면 최대한 공식링크를 이용해주시면 좋을거 같습니다. 13 | 14 | 한글(비공식, 번역진행중): 15 | > [Discord.js Korea](https://github.com/discordjs-kr) 팀에서 번역하고 있는 비공식 라이브러리 입니다. 16 | -------------------------------------------------------------------------------- /3화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const client = new Discord.Client(); 3 | const fs = require('fs') 4 | const config = require("./config.json") // config를 선언하여 config.json이 필요하다고 선언하였습니다. 5 | client.on("ready", () => { 6 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); 7 | }); 8 | client.commands = new Discord.Collection() // commands를 discord.collection을 사용하였습니다. 9 | client.aliases = new Discord.Collection()// aliases를 discord.collection을 사용하였습니다. 10 | 11 | fs.readdirSync("./Commands/").forEach(dir => { // fs 모듈을 이용하여 ./Commands/ 폴더 안에 내용을 불러와 forEach를 합니다 이후에 선언되는 것은 dir로 합시다. 12 | const Filter = fs.readdirSync(`./Commands/${dir}`).filter(f => f.endsWith(".js")); // Filter를 선언하여 Commands/여러 폴더들을 안에 .js로 끝나는 것들로 필터링 합시다. 13 | Filter.forEach(file => { // 안에 .js 파일들을 forEach하여 이후에 선언되는 것은 file로 합시다 14 | const cmd = require(`./Commands/${dir}/${file}`); // cmd를 선언하여 ./Commands/${dir}/${file}가 필요하다고 합시다 15 | client.commands.set(cmd.config.name, cmd) // 우리가 콜랙션으로 지정한 것들을 지정해줍시다 (안에 괄호는 그 안에 있는 config에 name을 지정하고 명령어에 이름을 저장합니다.) 16 | for (let alias of cmd.config.aliases) { // alias를 선언하고 cmd안에 있는 config.aliases가 여러개 있을텐데 그것들을 for로 반복합시다 17 | client.aliases.set(alias, cmd.config.name) // 콜렉션으로 지정한 것들을 다 넣어줍시다 18 | } 19 | }) 20 | }) 21 | 22 | 23 | function runCommand(command, message, args, prefix) { // 명령어를 실행할 때 쓸 함수입니다! 24 | if (client.commands.get(command) || client.aliases.get(command)) { // 만약에 command가 있는가 이후 true 25 | const cmd = client.commands.get(command) || client.commands.get(client.aliases.get(command)) // cmd를 따로 선언합시다 (해당 명령어를 get하여 불러오고) 26 | if (cmd) cmd.run(client, message, args, prefix); return // 만약에 cmd가 있다면 그 커멘드를 실행시켜줍니다. 27 | } 28 | } 29 | client.on("message", async msg => { 30 | const prefix = "튜토야 " // 접두사(prefix)를 저는 튜토야 라고 선정하였습니다! 31 | if (!msg.content.startsWith(prefix)) return // 만약에 메세지 내용이 접두사로 시작하지 않으면 return 32 | let args = msg.content.slice(prefix.length).trim().split(/ +/g) // argument 33 | let command = args.shift().toLowerCase() // 커멘드에 대한 이름을 선언 34 | try { // 아래의 명령어를 시도합니다. 35 | runCommand(command, msg, args, prefix) // 위에서 함수로 선언한 것을 command, msg(message), args(argument), prefix를 불러와 명령어를 실행 36 | } catch (e) { 37 | console.error(e) // 명령어를 실행하는 도중 에러가 발생하였다고 알려줍시다. 38 | } 39 | }) 40 | 41 | client.login(config.token) // config에 token 부분을 불러오게 합시다.} 42 | 43 | /** 44 | * 이제 Commands 폴더를 생성하고 Bot, Moderator 폴더를 생성후 명령어 소스는 아래와 같이 사용하시면 됩니다. 45 | exports.run = async (client, msg, args, prefix) => { 46 | // 여기 부분에 평소에 작성하던 코드를 이쪽에서 해주시면 되요! 47 | } 48 | 49 | exports.config = { 50 | name: 'name', // 말 그대로 봇 명령어 이름이에요 51 | aliases: ['aliases', 'aliases1', 'aliases2'], // 여기는 위에 봇 명령어 이름을 aliases, aliases1, aliases2 처럼 적은거대로 명령어를 실행하면 위에 코드가 작동해요 52 | category: ['bot'], // 여기는 카테고리에요. 자신이 원하는데로 카테고리를 분류해주시면 될꺼 같아요. 53 | des: ['config 설정방법'], // 여기는 해당 명령어에 대한 설명입니다. 54 | use: ['튜토야 exports 사용법'] // 당신이 원한다면 사용 방법을 적어주시면 다른 유저분들께 도움이 될꺼에요 (추후에 help 명령어 만들예정) 55 | } 56 | */ 57 | -------------------------------------------------------------------------------- /3화/example.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your bot token!" 3 | } -------------------------------------------------------------------------------- /3화/module_install.md: -------------------------------------------------------------------------------- 1 | ### 모듈을 설치하실 줄 모르신 분들을 위해서 작성하였습니다. 2 | 3 | 1. 먼저 설치하시기 전 개발하시던 폴더에 들어가주세요. 4 | 5 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711344453427658832/unknown.png) 6 | 7 | 2. 파일들이 쭈르르륵 있으실 텐데 그 위에 경로가 있을겁니다 그것을 클릭하고 cmd라고 적으신 후 엔터 눌러주세요. 8 | 9 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711344525217366086/unknown.png) 10 | 11 | 3. 그러면 해당 경로에 cmd가 켜졌을겁니다. 12 | 13 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711345543749828668/unknown.png) 14 | 15 | 4. 거기에 **npm i fs --save**라고 적어주세요. 16 | 17 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711344567835688960/unknown.png) 18 | 19 | 5. 그러고 엔터를 누르시면 아래의 사진처럼 바뀌었을겁니다! 20 | 21 | ![image](https://cdn.discordapp.com/attachments/708325535133990963/711344603504050207/unknown.png) 22 | 6. 모듈은 성공적으로 설치되었습니다! 23 | 이번 튜토리얼은 커멘드 핸들러 관련으로 제작되었습니다! 24 | 좀 어려운 과정이오니 천천히 설명을 잘 확인하며 따라와주세요! 25 | 26 | -------------------------------------------------------------------------------- /4화/Commands/Bot/embed.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | let embed = new Discord.MessageEmbed() // var -> let으로 수정하였습니다. 4 | .setTitle("여기는 대표 타이틀!") 5 | .setDescription("여기는 대표 설명!") 6 | .setColor("RED") 7 | .setFooter("여기는 말머리?") 8 | .setThumbnail("http://blogfiles.naver.net/20151023_23/shin_0305_1445573936921jrPRT_JPEG/%BD%E6%B3%D7%C0%CF%BF%B9%BD%C3.jpg") 9 | .setImage("http://blogfiles.naver.net/MjAxODA4MjNfMjQ0/MDAxNTM1MDE5ODk1Njc3.c5p_E9tLPEXGnXPAkpOuhpEOm7VLqopETMTfJ9C8CWYg.6FCsIDtjWnd19lSzmw_z1oHm9E7fd39s1RmRPeBOF3Ag.JPEG.dlawldbs20/VD-poem-20150915-01.jpg") 10 | .setTimestamp() 11 | .addField("여기는 소제목", "여기는 소설명(??)") 12 | msg.reply(embed) 13 | } 14 | 15 | exports.config = { 16 | name: '임베드', 17 | aliases: ['embed', 'dlaqpem'], 18 | category: ['bot'], 19 | des: ['임베드에 대한 설명'], 20 | use: ['튜토야 임베드'] 21 | } -------------------------------------------------------------------------------- /4화/Commands/Bot/ping.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | msg.channel.send(`${client.user.username}의 핑은 ${client.ws.ping}ms 입니다!`) 3 | } 4 | 5 | exports.config = { 6 | name: '핑', 7 | aliases: ['vld', 'botping'], 8 | category: ['bot'], 9 | des: ['봇의 디스코드 웹소켓 지연시간을 알려드립니다'], 10 | use: ['튜토야 핑'] 11 | } -------------------------------------------------------------------------------- /4화/Commands/Bot/webhook.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | const hook = new Discord.WebhookClient('WebHook ID', 'WebHook Token'); 4 | hook.send("Hello, new World!") 5 | } 6 | 7 | exports.config = { 8 | name: '웹훅', 9 | aliases: ['vld', 'botping'], 10 | category: ['bot'], 11 | des: ['웹훅에 대한 사용방법'], 12 | use: ['튜토야 웹훅'] 13 | } -------------------------------------------------------------------------------- /4화/Commands/crawling/docs.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const fetch = require("node-fetch") 3 | exports.run = async (client, msg, args, prefix) => { 4 | /** 5 | * 이것은 번외로 Discord.js 문서 api로 크롤링하여 임베드로 표현한 코드입니다. 6 | * Discord.js 라이브러리 읽는것이 익숙하지 않을 경우 이것을 사용하여 참고하시면 좋을거 같습니다 (실제로 저도 많이 사용하고 있습니다 ㅎㅎ) 7 | * 그러나 라이브러리를 읽는것이 익숙해지는 것을 권장합니다! 8 | * API URL: https://djsdocs.sorta.moe 9 | */ 10 | if (!args[0]) return msg.reply("❎ 쿼리를 입력해주세요.") // argument가 없다면 쿼리를 입력해달라고 전해줍시다. 11 | let query = encodeURI(args.join(" ")) // 쿼리를 선언하였고 검색에 사용할 것이오니 인코드를 해줍시다. 12 | fetch(`https://djsdocs.sorta.moe/v1/main/stable/embed?q=${query}`).then(a => a.json().then((r) => { // 해당 api를 fetch 합시다 a = fetch한 값들, 그걸 json화 하고 그 값들을 불러오기 위해서는 다시 then을 사용하여 r로 선언합니다. 13 | if (!r) return msg.reply("❎ 해당 쿼리는 존재하지 않습니다.") 14 | const docs = new Discord.MessageEmbed(r) // docs를 const로 선언을 해줍시다. 15 | docs.setFooter(msg.author.tag, msg.author.displayAvatarURL()) 16 | msg.reply(docs) 17 | })) 18 | } 19 | 20 | exports.config = { 21 | name: 'docs', 22 | aliases: ['djsdocs'], 23 | category: ['Crawling'], 24 | des: ['Discord.js 문서 api를 활용한 명령어 입니다'], 25 | use: ['튜토야 docs '] 26 | } -------------------------------------------------------------------------------- /4화/Commands/moderator/ban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("차단하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.ban(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 차단 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '차단', 19 | aliases: ['ban', '벤'], 20 | category: ['moderator'], 21 | des: ['유저를 해당 서버에서 차단 시킵니다.'], 22 | use: ['튜토야 차단 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /4화/Commands/moderator/clear.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | if (!args[0]) return msg.reply("청소할 만큼의 값을 정수로 적어주세요!") 3 | if (!Number(args[0])) return msg.reply("메세지를 지울 값이 숫자가 아니면 안되요!") 4 | if (args[0] < 1) return msg.reply("메세지를 지울 값을 1보다 작게 하시면 안되요!") 5 | if (args[0] > 100) return msg.reply("메세지를 지울 값이 100보다 크면 메세지가 안지워져요!") 6 | 7 | msg.channel.bulkDelete(args[0]).then(msg.reply(`성공적으로 ${args[0]}개 만큼 메세지를 삭제하였습니다!`)) 8 | } 9 | 10 | exports.config = { 11 | name: '청소', 12 | aliases: ['clear', 'clean'], 13 | category: ['moderator'], 14 | des: ['bulkdelete'], 15 | use: ['튜토야 청소 <청소 할 메세지의 수>'] 16 | } -------------------------------------------------------------------------------- /4화/Commands/moderator/kick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("추방하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.kick(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 킥 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '추방', 19 | aliases: ['킥', 'kick'], 20 | category: ['moderator'], 21 | des: ['유저를 강제퇴장 시킵니다.'], 22 | use: ['튜토야 추방 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /4화/README.MD: -------------------------------------------------------------------------------- 1 | ### 잠시 튜토리얼을 보시기 전 읽어주세요. 2 | 3 | 이번 4화는 **웹에 있는 json을 불러와 사용하는 방법**에 대한 튜토리얼을 적었습니다. 4 | 이번에 필요한 모듈은 [**node-fetch**](https://www.npmjs.com/package/node-fetch), [**Moment-Timezone**](https://www.npmjs.com/package/moment-timezone) 모듈이 필요하며 5 | 이전과 **동일한 방법으로 모듈을 설치**해주시면 됩니다. 6 | 7 | 3화에서 많은 이슈들이 나왔는데 8 | **튜토리얼을 보고 계신분들을 생각하며 튜토리얼을 작성히지 못한 점에 대해서는 매우 죄송**하다고 생각합니다... 9 | 10 | 이슈가 안나오게끔 **열심히 튜토리얼을 제작할테니 많은 관심**을 주시면 감사하겠습니다! 11 | -------------------------------------------------------------------------------- /4화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const client = new Discord.Client(); 3 | const fs = require('fs') 4 | const config = require("./config.json") 5 | client.on("ready", () => { 6 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); 7 | }); 8 | client.commands = new Discord.Collection() 9 | client.aliases = new Discord.Collection() 10 | 11 | fs.readdirSync("./Commands/").forEach(dir => { 12 | const Filter = fs.readdirSync(`./Commands/${dir}`).filter(f => f.endsWith(".js")); 13 | Filter.forEach(file => { 14 | const cmd = require(`./Commands/${dir}/${file}`); 15 | client.commands.set(cmd.config.name, cmd) 16 | for (let alias of cmd.config.aliases) { 17 | client.aliases.set(alias, cmd.config.name) 18 | } 19 | }) 20 | }) 21 | 22 | 23 | function runCommand(command, message, args, prefix) { 24 | if (client.commands.get(command) || client.aliases.get(command)) { 25 | const cmd = client.commands.get(command) || client.commands.get(client.aliases.get(command)) 26 | if (cmd) cmd.run(client, message, args, prefix); return 27 | } 28 | } 29 | client.on("message", async msg => { 30 | const prefix = "튜토야 " 31 | if(!msg.content.startsWith(prefix)) return 32 | let args = msg.content.slice(prefix.length).trim().split(/ +/g) 33 | let command = args.shift().toLowerCase() 34 | try { 35 | runCommand(command, msg, args, prefix) 36 | } catch (e) { 37 | console.error(e) 38 | } 39 | }) 40 | 41 | client.login(config.token) 42 | -------------------------------------------------------------------------------- /4화/example.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your bot token!" 3 | } -------------------------------------------------------------------------------- /5화/Commands/Bot/embed.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | let embed = new Discord.MessageEmbed() // var -> let으로 수정하였습니다. 4 | .setTitle("여기는 대표 타이틀!") 5 | .setDescription("여기는 대표 설명!") 6 | .setColor("RED") 7 | .setFooter("여기는 말머리?") 8 | .setThumbnail("http://blogfiles.naver.net/20151023_23/shin_0305_1445573936921jrPRT_JPEG/%BD%E6%B3%D7%C0%CF%BF%B9%BD%C3.jpg") 9 | .setImage("http://blogfiles.naver.net/MjAxODA4MjNfMjQ0/MDAxNTM1MDE5ODk1Njc3.c5p_E9tLPEXGnXPAkpOuhpEOm7VLqopETMTfJ9C8CWYg.6FCsIDtjWnd19lSzmw_z1oHm9E7fd39s1RmRPeBOF3Ag.JPEG.dlawldbs20/VD-poem-20150915-01.jpg") 10 | .setTimestamp() 11 | .addField("여기는 소제목", "여기는 소설명(??)") 12 | msg.reply(embed) 13 | } 14 | 15 | exports.config = { 16 | name: '임베드', 17 | aliases: ['embed', 'dlaqpem'], 18 | category: ['bot'], 19 | des: ['임베드에 대한 설명'], 20 | use: ['튜토야 임베드'] 21 | } -------------------------------------------------------------------------------- /5화/Commands/Bot/help.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | if (!args[0]) { 4 | const categorys = client.category // bot.js에 있는 client.category 를 categorys로 선언하였습니다. 5 | let Commands = new Discord.MessageEmbed() 6 | .setAuthor(client.user.username + " 봇 명령어", client.user.displayAvatarURL()) 7 | .setColor("7289DA") 8 | .setFooter(`${prefix}도움 <명령어> 를 통하여 해당 명령어를 자세히 확인해보세요.`) 9 | for (const category of categorys) { // 위에서 선언한 것을 for문으로 category로 선언하였습니다. (반복문) 10 | /** 11 | * 위에 for문을 잠시 설명해드리자면 12 | * categorys가 있는 만큼 반복을 하여 13 | * categorys중에 하나를 category로 선언하여 아래의 코드를 작성한 것입니다. 14 | */ 15 | Commands.addField(category, `> **\`${client.commands.filter(el => el.config.category == category).keyArray().join('`, `')}\`**`) 16 | /** 17 | * 소제목에는 카테고리 이름을 넣고 18 | * 소 설명에는 이전에 client.commands를 컬랙션으로 지정한 것들을 불러와 필터를 시켜줍니다. 19 | * el로 선언하여 el이 어떤식으로 되어있냐면 20 | * 이전에 명령어들에게 exports를 사용하여 모듈화 하였는데 그것들을 불러오게 됩니다. 21 | * 여러가지 있는데 그중에 하나만 뜯어서 보여드렸습니다. 22 | * { 23 | * run: [AsyncFunction], 24 | * config: { 25 | * name: "asd", 26 | * aliases: ["asd1", "asd2", "123"], 27 | * category: ["example"], 28 | * des: ["config 설명"], 29 | * use: ["튜토야 도움 <명령어>"] 30 | * } 31 | * } 32 | * 그러고 이제 config.category가 const로 선언한 category와 맞는지 확인 후 Array화 시킨 후 안에 있는 명령어들을 불러온 것입니다. 33 | */ 34 | } 35 | msg.reply(Commands) 36 | } else { 37 | if (client.commands.get(args[0])) { // 만약에 client.commands안에 args[0] 라는게 있다면 38 | var command = client.commands.get(args[0]) // command 는 client.commands.get(args[0])로 선언해줍시다. 39 | } else if (client.aliases.get(args[0])) { // 만약에 client.aliases 안에 args[0] 라는게 있다면 40 | let aliases = client.aliases.get(args[0]) // aliases 를 client.aliases.get(args[0])로 선언해주고 (왜냐하면 저기서 aliases를 불러오면 command 이름이 나오기 때문입니다.) 41 | var command = client.commands.get(aliases) // commands는 client.commands.get(aliases)으로 선언해줍시다. 42 | } else return msg.reply(`${args[0]} 명령어라는 것을 찾을 수 없어요...`) 43 | 44 | let config = command.config 45 | let name = config.name 46 | let aliases = config.aliases 47 | let category = config.category 48 | let description = config.des 49 | let use = config.use 50 | 51 | let Command = new Discord.MessageEmbed() 52 | .setTitle(`${name} 명령어`) 53 | .setColor("7289DA") 54 | .setDescription(`\`\`\`fix\n사용법: ${use}\`\`\``) 55 | .addField("명령어 설명", `**${description}**`, false) 56 | .addField("카테고리", `**${category}**`, true) 57 | .addField("공유하는 명령어", `**${aliases}**`, true) 58 | msg.reply(Command) 59 | } 60 | } 61 | 62 | exports.config = { 63 | name: '도움말', 64 | aliases: ['도움', '명령어', 'commands', 'help'], 65 | category: ['bot'], 66 | des: ['봇에 대한 명령어 리스트들을 불러와드립니다.'], 67 | use: ['튜토야 도움말 <명령어>'] 68 | } -------------------------------------------------------------------------------- /5화/Commands/Bot/ping.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | msg.channel.send(`${client.user.username}의 핑은 ${client.ws.ping}ms 입니다!`) 3 | } 4 | 5 | exports.config = { 6 | name: '핑', 7 | aliases: ['vld', 'botping'], 8 | category: ['bot'], 9 | des: ['봇의 디스코드 웹소켓 지연시간을 알려드립니다'], 10 | use: ['튜토야 핑'] 11 | } -------------------------------------------------------------------------------- /5화/Commands/Bot/webhook.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | const hook = new Discord.WebhookClient('WebHook ID', 'WebHook Token'); 4 | hook.send("Hello, new World!") 5 | } 6 | 7 | exports.config = { 8 | name: '웹훅', 9 | aliases: ['vld', 'botping'], 10 | category: ['bot'], 11 | des: ['웹훅에 대한 사용방법'], 12 | use: ['튜토야 웹훅'] 13 | } -------------------------------------------------------------------------------- /5화/Commands/Owner/eval.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | if (!client.devs.includes(msg.author.id)) return msg.reply("이 명령어는 봇 관리자만 사용할 수 있습니다.") // bot.js에서 client.devs를 저장한 것을 불러와 포함하지 않으면 해당 메세지로 답변해줍시다. 4 | 5 | const coode = args.join(" ") 6 | const module = "const Discord = require(\"discord.js\")" 7 | if (!coode) return msg.reply("실행할 코드를 입력해주세요.") 8 | new Promise(res => res(eval(coode))).then(code => { // Promise를 생성하여 eval(coode)를 해준 후 then을 사용하여 그것들을 code로 선언해줍시다. 9 | if (typeof code !== 'string') code = require('util').inspect(code, { depth: 0 }) // 해당 코드가 스트링이 아니라면 code는 util 이라는 모듈 안에 있는 inspect라는 함수를 이용하여 정리 해줍시다. 10 | /** 11 | * util.inspect에 대해 자세히 알고 싶다면 아래의 링크를 클릭해주세요. 12 | * https://nodejs.org/api/util.html#util_util_inspect_object_options 13 | * */ 14 | let evaled = new Discord.MessageEmbed() 15 | .setTitle("✅ Code Execution") 16 | .setColor("7289DA") 17 | .addField("📥 **Input**", `\`\`\`js\n${module}\n\n${coode}\`\`\``, false) 18 | .addField("📤 **Output**", `\`\`\`js\n${code}\`\`\``, false) 19 | msg.reply(evaled) 20 | /** 21 | * 이제 튜토야 이블 msg를 실행 시켜주면 아래와 같은 결과 값이 나와요! 22 | * Message { 23 | channel: [TextChannel], 24 | deleted: false, 25 | id: '717685629969891369', 26 | type: 'DEFAULT', 27 | content: '튜토야 이블 msg', 28 | author: [User], 29 | pinned: false, 30 | tts: false, 31 | nonce: '717685619970539520', 32 | system: false, 33 | embeds: [], 34 | attachments: Collection [Map] {}, 35 | createdTimestamp: 1591179988139, 36 | editedTimestamp: null, 37 | reactions: [ReactionManager], 38 | mentions: [MessageMentions], 39 | webhookID: null, 40 | application: null, 41 | activity: null, 42 | _edits: [], 43 | flags: [MessageFlags], 44 | reference: null 45 | } 46 | */ 47 | }).catch(e => { // 해당 코드에서 에러가 발생하면 캐치를 하고 그 에러 값을 e로 선언해줍시다. 48 | let evaled = new Discord.MessageEmbed() 49 | .setTitle("❎ Code Execution") 50 | .setColor("RED") 51 | .setDescription(`\`\`\`js\n${e}\`\`\``) 52 | msg.reply(evaled) 53 | }) 54 | } 55 | 56 | exports.config = { 57 | name: '이블', 58 | aliases: ['eval'], 59 | category: ['owner'], 60 | des: ['코드 실행기'], 61 | use: ['튜토야 이블 <코드>'] 62 | } -------------------------------------------------------------------------------- /5화/Commands/crawling/NaverRanking.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const fetch = require("node-fetch") 3 | const moment = require('moment-timezone') 4 | exports.run = async (client, msg, args, prefix) => { 5 | fetch("http://rank.search.naver.com/rank.js").then(r => r.json().then((r) => { 6 | let updated = moment(new Date(r.ts)).locale("ko").format("llll") 7 | console.log(r.data[0].data) 8 | let data = r.data[0].data 9 | let NaverRanking = new Discord.MessageEmbed() 10 | .setTitle("네이버 실시간 검색어 순위") 11 | .setColor("#83ff7b") 12 | .setFooter(updated) 13 | for (let i = 0; i < 10; i++) { 14 | NaverRanking.addField(`${data[i].rank}위`, `[${data[i].keyword}](https://search.naver.com/search.naver?where=nexearch&query=${encodeURI(data[i].keyword)}&sm=top_lve.agallgrpmamsi0en0sp0&ie=utf8)`) 15 | } 16 | msg.channel.send(NaverRanking) 17 | })) 18 | } 19 | 20 | exports.config = { 21 | name: '네이버차트', 22 | aliases: ['NaverRank', 'NaverRanking'], 23 | category: ['crawling'], 24 | des: ['네이버 랭킹 json을 크롤링하여 값을 불러왔습니다.'], 25 | use: ['튜토야 네이버차트'] 26 | } -------------------------------------------------------------------------------- /5화/Commands/crawling/docs.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const fetch = require("node-fetch") 3 | exports.run = async (client, msg, args, prefix) => { 4 | if (!args[0]) return msg.reply("❎ 쿼리를 입력해주세요.") 5 | let query = encodeURI(args.join(" ")) 6 | fetch(`https://djsdocs.sorta.moe/v1/main/stable/embed?q=${query}`).then(a => a.json().then((r) => { 7 | if (!r) return msg.reply("❎ 해당 쿼리는 존재하지 않습니다.") 8 | const docs = new Discord.MessageEmbed(r) 9 | docs.setFooter(msg.author.tag, msg.author.displayAvatarURL()) 10 | msg.reply(docs) 11 | })) 12 | } 13 | 14 | exports.config = { 15 | name: 'docs', 16 | aliases: ['djsdocs'], 17 | category: ['crawling'], 18 | des: ['Discord.js 문서 api를 활용한 명령어 입니다'], 19 | use: ['튜토야 docs '] 20 | } -------------------------------------------------------------------------------- /5화/Commands/moderator/ban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("차단하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.ban(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 차단 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '차단', 19 | aliases: ['ban', '벤'], 20 | category: ['moderator'], 21 | des: ['유저를 해당 서버에서 차단 시킵니다.'], 22 | use: ['튜토야 차단 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /5화/Commands/moderator/clear.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | if (!args[0]) return msg.reply("청소할 만큼의 값을 정수로 적어주세요!") 3 | if (!Number(args[0])) return msg.reply("메세지를 지울 값이 숫자가 아니면 안되요!") 4 | if (args[0] < 1) return msg.reply("메세지를 지울 값을 1보다 작게 하시면 안되요!") 5 | if (args[0] > 100) return msg.reply("메세지를 지울 값이 100보다 크면 메세지가 안지워져요!") 6 | 7 | msg.channel.bulkDelete(args[0]).then(msg.reply(`성공적으로 ${args[0]}개 만큼 메세지를 삭제하였습니다!`)) 8 | } 9 | 10 | exports.config = { 11 | name: '청소', 12 | aliases: ['clear', 'clean'], 13 | category: ['moderator'], 14 | des: ['bulkdelete'], 15 | use: ['튜토야 청소 <청소 할 메세지의 수>'] 16 | } -------------------------------------------------------------------------------- /5화/Commands/moderator/kick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("추방하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.kick(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 킥 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '추방', 19 | aliases: ['킥', 'kick'], 20 | category: ['moderator'], 21 | des: ['유저를 강제퇴장 시킵니다.'], 22 | use: ['튜토야 추방 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /5화/README.MD: -------------------------------------------------------------------------------- 1 | ### 잠시 튜토리얼을 보시기 전 읽어주세요. 2 | 3 | 이번 5화는 도움말, eval (이블)을 만드는 방법에 대해 튜토리얼을 적어볼려고 합니다! 4 | 폴더랑 파일 명은 제대로 작업해주시고 오늘도 화이팅해서 5화를 진행해보도록 합시다:) 5 | 6 | 이번에 필요한 모듈은 [fs](https://www.npmjs.com/package/fs) 입니다. 7 | 8 | 메인 파일에 여러가지 추가하였으니 확인해주세요! 9 | 10 | 이번에도 코드 작성하시다가 에러가 발생하신다면 Issue, PR를 넣어주세요. -------------------------------------------------------------------------------- /5화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const client = new Discord.Client(); 3 | const fs = require('fs') 4 | const config = require("./config.json") 5 | client.on("ready", () => { 6 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); 7 | }); 8 | client.commands = new Discord.Collection() 9 | client.aliases = new Discord.Collection() 10 | client.devs = ['570617337691111444', "1234567890"] // devs에 제 고유 아이디를 넣어놨습니다! 그리고 이런식으로 아이디를 넣으면 그 사람들도 공유가 됩니다! (eval에서 권한 체크용으로 쓰일 예정) 11 | client.category = ['bot', 'crawling', 'moderator', 'owner'] // help 명령어에서 카테고리들을 쉽게 불러오도록 이미 Array(어레이)를 만들었습니다. 12 | fs.readdirSync("./Commands/").forEach(dir => { 13 | const Filter = fs.readdirSync(`./Commands/${dir}`).filter(f => f.endsWith(".js")); 14 | Filter.forEach(file => { 15 | const cmd = require(`./Commands/${dir}/${file}`); 16 | client.commands.set(cmd.config.name, cmd) 17 | for (let alias of cmd.config.aliases) { 18 | client.aliases.set(alias, cmd.config.name) 19 | } 20 | }) 21 | }) 22 | 23 | 24 | function runCommand(command, message, args, prefix) { 25 | if (client.commands.get(command) || client.aliases.get(command)) { 26 | const cmd = client.commands.get(command) || client.commands.get(client.aliases.get(command)) 27 | if (cmd) cmd.run(client, message, args, prefix); return 28 | } 29 | } 30 | client.on("message", async msg => { 31 | const prefix = "튜토야 " 32 | if (!msg.content.startsWith(prefix)) return 33 | let args = msg.content.slice(prefix.length).trim().split(/ +/g) 34 | let command = args.shift().toLowerCase() 35 | try { 36 | runCommand(command, msg, args, prefix) 37 | } catch (e) { 38 | console.error(e) 39 | } 40 | }) 41 | 42 | client.login(config.token) 43 | -------------------------------------------------------------------------------- /5화/example.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your bot token!" 3 | } -------------------------------------------------------------------------------- /6화/Commands/Bot/embed.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | let embed = new Discord.MessageEmbed() // var -> let으로 수정하였습니다. 4 | .setTitle("여기는 대표 타이틀!") 5 | .setDescription("여기는 대표 설명!") 6 | .setColor("RED") 7 | .setFooter("여기는 말머리?") 8 | .setThumbnail("http://blogfiles.naver.net/20151023_23/shin_0305_1445573936921jrPRT_JPEG/%BD%E6%B3%D7%C0%CF%BF%B9%BD%C3.jpg") 9 | .setImage("http://blogfiles.naver.net/MjAxODA4MjNfMjQ0/MDAxNTM1MDE5ODk1Njc3.c5p_E9tLPEXGnXPAkpOuhpEOm7VLqopETMTfJ9C8CWYg.6FCsIDtjWnd19lSzmw_z1oHm9E7fd39s1RmRPeBOF3Ag.JPEG.dlawldbs20/VD-poem-20150915-01.jpg") 10 | .setTimestamp() 11 | .addField("여기는 소제목", "여기는 소설명(??)") 12 | msg.reply(embed) 13 | } 14 | 15 | exports.config = { 16 | name: '임베드', 17 | aliases: ['embed', 'dlaqpem'], 18 | category: ['bot'], 19 | des: ['임베드에 대한 설명'], 20 | use: ['튜토야 임베드'] 21 | } -------------------------------------------------------------------------------- /6화/Commands/Bot/help.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async(client, msg, args, prefix) => { 3 | if (!args[0]) { 4 | const categorys = client.category 5 | let Commands = new Discord.MessageEmbed() 6 | .setAuthor(client.user.username + " 봇 명령어", client.user.displayAvatarURL()) 7 | .setColor("7289DA") 8 | .setFooter(`${prefix}도움 <명령어> 를 통하여 해당 명령어를 자세히 확인해보세요.`) 9 | for (const category of categorys) { 10 | Commands.addField(category, `> **\`${client.commands.filter(el => el.config.category == category).keyArray().join('`, `')}\`**`) 11 | 12 | } 13 | msg.reply(Commands) 14 | } else { 15 | if (client.commands.get(args[0])) { 16 | var command = client.commands.get(args[0]) 17 | } else if (client.aliases.get(args[0])) { 18 | let aliases = client.aliases.get(args[0]) 19 | var command = client.commands.get(aliases) 20 | } else return msg.reply(`${args[0]} 명령어라는 것을 찾을 수 없어요...`) 21 | 22 | let config = command.config 23 | let name = config.name 24 | let aliases = config.aliases 25 | let category = config.category 26 | let description = config.des 27 | let use = config.use 28 | 29 | let Command = new Discord.MessageEmbed() 30 | .setTitle(`${name} 명령어`) 31 | .setColor("7289DA") 32 | .setDescription(`\`\`\`fix\n사용법: ${use}\`\`\``) 33 | .addField("명령어 설명", `**${description}**`, false) 34 | .addField("카테고리", `**${category}**`, true) 35 | .addField("공유하는 명령어", `**${aliases}**`, true) 36 | msg.reply(Command) 37 | } 38 | } 39 | 40 | exports.config = { 41 | name: '도움말', 42 | aliases: ['도움', '명령어', 'commands', 'help'], 43 | category: ['bot'], 44 | des: ['봇에 대한 명령어 리스트들을 불러와드립니다.'], 45 | use: ['튜토야 도움말 <명령어>'] 46 | } -------------------------------------------------------------------------------- /6화/Commands/Bot/ping.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | msg.channel.send(`${client.user.username}의 핑은 ${client.ws.ping}ms 입니다!`) 3 | } 4 | 5 | exports.config = { 6 | name: '핑', 7 | aliases: ['vld', 'botping'], 8 | category: ['bot'], 9 | des: ['봇의 디스코드 웹소켓 지연시간을 알려드립니다'], 10 | use: ['튜토야 핑'] 11 | } -------------------------------------------------------------------------------- /6화/Commands/Bot/webhook.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async (client, msg, args, prefix) => { 3 | const hook = new Discord.WebhookClient('WebHook ID', 'WebHook Token'); 4 | hook.send("Hello, new World!") 5 | } 6 | 7 | exports.config = { 8 | name: '웹훅', 9 | aliases: ['vld', 'botping'], 10 | category: ['bot'], 11 | des: ['웹훅에 대한 사용방법'], 12 | use: ['튜토야 웹훅'] 13 | } -------------------------------------------------------------------------------- /6화/Commands/Owner/eval.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | exports.run = async(client, msg, args, prefix) => { 3 | if (!client.devs.includes(msg.author.id)) return msg.reply("이 명령어는 봇 관리자만 사용할 수 있습니다.") 4 | 5 | const coode = args.join(" ") 6 | const module = "const Discord = require(\"discord.js\")" 7 | if (!coode) return msg.reply("실행할 코드를 입력해주세요.") 8 | new Promise(res => res(eval(coode))).then(code => { 9 | if (typeof code !== 'string') code = require('util').inspect(code, { depth: 0 }) 10 | let evaled = new Discord.MessageEmbed() 11 | .setTitle("✅ Code Execution") 12 | .setColor("7289DA") 13 | .addField("📥 **Input**", `\`\`\`js\n${module}\n\n${coode}\`\`\``, false) 14 | .addField("📤 **Output**", `\`\`\`js\n${code}\`\`\``, false) 15 | msg.reply(evaled) 16 | 17 | }).catch(e => { 18 | let evaled = new Discord.MessageEmbed() 19 | .setTitle("❎ Code Execution") 20 | .setColor("RED") 21 | .setDescription(`\`\`\`js\n${e}\`\`\``) 22 | msg.reply(evaled) 23 | }) 24 | } 25 | 26 | exports.config = { 27 | name: '이블', 28 | aliases: ['eval'], 29 | category: ['owner'], 30 | des: ['코드 실행기'], 31 | use: ['튜토야 이블 <코드>'] 32 | } -------------------------------------------------------------------------------- /6화/Commands/crawling/NaverRanking.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const fetch = require("node-fetch") 3 | const moment = require('moment-timezone') 4 | exports.run = async (client, msg, args, prefix) => { 5 | fetch("http://rank.search.naver.com/rank.js").then(r => r.json().then((r) => { 6 | let updated = moment(new Date(r.ts)).locale("ko").format("llll") 7 | console.log(r.data[0].data) 8 | let data = r.data[0].data 9 | let NaverRanking = new Discord.MessageEmbed() 10 | .setTitle("네이버 실시간 검색어 순위") 11 | .setColor("#83ff7b") 12 | .setFooter(updated) 13 | for (let i = 0; i < 10; i++) { 14 | NaverRanking.addField(`${data[i].rank}위`, `[${data[i].keyword}](https://search.naver.com/search.naver?where=nexearch&query=${encodeURI(data[i].keyword)}&sm=top_lve.agallgrpmamsi0en0sp0&ie=utf8)`) 15 | } 16 | msg.channel.send(NaverRanking) 17 | })) 18 | } 19 | 20 | exports.config = { 21 | name: '네이버차트', 22 | aliases: ['NaverRank', 'NaverRanking'], 23 | category: ['crawling'], 24 | des: ['네이버 랭킹 json을 크롤링하여 값을 불러왔습니다.'], 25 | use: ['튜토야 네이버차트'] 26 | } -------------------------------------------------------------------------------- /6화/Commands/crawling/docs.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const fetch = require("node-fetch") 3 | exports.run = async (client, msg, args, prefix) => { 4 | if (!args[0]) return msg.reply("❎ 쿼리를 입력해주세요.") 5 | let query = encodeURI(args.join(" ")) 6 | fetch(`https://djsdocs.sorta.moe/v1/main/stable/embed?q=${query}`).then(a => a.json().then((r) => { 7 | if (!r) return msg.reply("❎ 해당 쿼리는 존재하지 않습니다.") 8 | const docs = new Discord.MessageEmbed(r) 9 | docs.setFooter(msg.author.tag, msg.author.displayAvatarURL()) 10 | msg.reply(docs) 11 | })) 12 | } 13 | 14 | exports.config = { 15 | name: 'docs', 16 | aliases: ['djsdocs'], 17 | category: ['crawling'], 18 | des: ['Discord.js 문서 api를 활용한 명령어 입니다'], 19 | use: ['튜토야 docs '] 20 | } -------------------------------------------------------------------------------- /6화/Commands/database/gamble.js: -------------------------------------------------------------------------------- 1 | // 모듈 불러오기 2 | const Discord = require("discord.js") 3 | const reload = require("self-reload-json") 4 | const User = new reload("./json/user.json") 5 | 6 | exports.run = async(client, msg, args, prefix) => { 7 | // 무작위숫자 함수 8 | function getRandomInt(min, max) { 9 | return Math.floor(Math.random() * (max - min)) + min; 10 | } 11 | // user[msg.author.id].money가 0보다 작거나 같을 경우 12 | if (User[msg.author.id].money <= 0) { 13 | let nomoney = new Discord.MessageEmbed() 14 | .setDescription("❎ 당신은 도박을 할 돈이 없습니다..") 15 | .setColor("RED") 16 | return msg.reply(nomoney) 17 | } 18 | 19 | let help = new Discord.MessageEmbed() 20 | .setDescription(`${prefix}도박 <돈>`) 21 | .setColor("7289DA") 22 | .setFooter("돈 부분은 무조건 정수로 적어주세요.") 23 | 24 | // args[0]이 비어있을 경우 25 | if (!args[0]) return msg.reply(help) 26 | 27 | // args[0]이 숫자가 아닐경우 28 | if (!Number(args[0])) return msg.reply(help) 29 | 30 | let gamble = new Discord.MessageEmbed() 31 | .setDescription(`정말로 \`${args[0]}₩\` 만큼 도박을 진행하시겠습니까?`) 32 | .setColor("7289DA") 33 | 34 | msg.reply(gamble).then(async message => { 35 | // 반응 추가 (✅, ❎) 36 | await message.react("✅") 37 | await message.react("❎") 38 | 39 | // 필터 생성 40 | let filterYes = (reaction, user) => reaction.emoji.name === '✅' && user.id == msg.author.id 41 | 42 | // 반응 콜랙터 생성 43 | let collectorYes = message.createReactionCollector(filterYes, { max: 1, time: 60000 }) // max: 1 -> 반응은 한번만, time: 60000 -> 제한시간 60초 44 | 45 | // 위에 콜랙터 이벤트 생성 46 | collectorYes.on('collect', () => { // 반응을 할 경우 47 | message.delete() 48 | 49 | // 배율 설정 50 | let multiplication = getRandomInt(2, 10) 51 | 52 | // 성공하면 받을 금액 53 | let money = args[0] * multiplication 54 | 55 | // 성공과 실패를 나눌 숫자 56 | let boolean = getRandomInt(1, 3) 57 | 58 | // boolean 변수가 1일 경우 59 | if (boolean == 1) { 60 | let yourmoney = User[msg.author.id].money + money 61 | let success = new Discord.MessageEmbed() 62 | .setDescription(`당신은 도박을 성공하여 ${multiplication}배 만큼 거신 금액을 얻으셨습니다.\n\n 보유하고 있는 돈: ||${yourmoney}₩||`) 63 | .setColor("7289DA") 64 | msg.reply(success) 65 | 66 | // user의 money는 money + money <- (위에 선언한 money) 67 | User[msg.author.id].money = User[msg.author.id].money + money 68 | 69 | // user 저장 70 | User.save() 71 | 72 | // 아닐경우 73 | } else { 74 | // 임베드에 표기할 숫자 75 | let yourmoney = User[msg.author.id].money - args[0] 76 | let fail = new Discord.MessageEmbed() 77 | .setDescription(`당신은 도박을 실패하여 거신 금액을 모두 잃었습니다.\n\n 보유하고 있는 돈: ||${yourmoney}₩||`) 78 | .setColor("7289DA") 79 | msg.reply(fail) 80 | 81 | // user의 money는 money - args[0] (건 금액) 82 | User[msg.author.id].money = User[msg.author.id].money - args[0] 83 | 84 | // user의 json을 저장 85 | User.save() 86 | } 87 | }) 88 | 89 | // 위의 콜랙터의 이벤트 생성 90 | collectorYes.on('end', (_, reason) => { // 이벤트가 끝날 경우 91 | if (reason === "time") { // 이벤트가 끝난 사유가 time일 경우 92 | message.delete() 93 | let timeover = new Discord.MessageEmbed() 94 | .setDescription("시간이 초과가 되었으니 다시 시도해주세요.") 95 | .setColor("RED") 96 | msg.reply(timeover) 97 | } 98 | }) 99 | 100 | // 필터 생성 101 | let filterNo = (reaction, user) => reaction.emoji.name === '❎' && user.id === msg.author.id 102 | 103 | // 콜랙터 생성 104 | let collectorNo = message.createReactionCollector(filterNo, { max: 1, time: 60000 }) 105 | // 콜랙터 이벤트 생성 106 | collectorNo.on('collect', () => { // 반응을 할 경우 107 | message.delete() 108 | let cancel = new Discord.MessageEmbed() 109 | .setDescription("도박 진행을 취소하였습니다.") 110 | .setColor("RED") 111 | msg.reply(cancel) 112 | }) 113 | }) 114 | } 115 | 116 | exports.config = { 117 | name: '도박', 118 | aliases: ['gamble'], 119 | category: ['database'], 120 | des: ['당신이 돈을 걸어 반반의 확률로 게임을 진행합니다.'], 121 | use: ['튜토야 도박 <돈>'] 122 | } -------------------------------------------------------------------------------- /6화/Commands/database/money.js: -------------------------------------------------------------------------------- 1 | // 모듈 불러오기 2 | const Discord = require("discord.js") 3 | const reload = require("self-reload-json") 4 | const User = new reload("./json/user.json") 5 | 6 | exports.run = async(client, msg, args, prefix) => { 7 | let money = new Discord.MessageEmbed() 8 | // user.money를 아래와 같이 불러옴. 9 | .setDescription(`보유하고 있는 금액: ${User[msg.author.id].money}₩`) 10 | .setColor("7289DA") 11 | msg.reply(money) 12 | } 13 | 14 | exports.config = { 15 | name: '돈', 16 | aliases: ['내돈', 'money', 'mymoney'], 17 | category: ['database'], 18 | des: ['당신이 보유하고 있는 금액을 알려드립니다.'], 19 | use: ['튜토야 돈'] 20 | } -------------------------------------------------------------------------------- /6화/Commands/database/registerGuild.js: -------------------------------------------------------------------------------- 1 | // 모듈 불러오기 2 | const Discord = require("discord.js") 3 | const reload = require("self-reload-json") 4 | const Guild = new reload("./json/server.json") 5 | 6 | exports.run = async(client, msg, args, prefix) => { 7 | let already = new Discord.MessageEmbed() 8 | .setDescription("❎ 이 서버는 이미 가입되어 있습니다.") 9 | .setColor("RED") 10 | 11 | // guild[msg.guild.id]가 있을 경우 12 | if (Guild[msg.guild.id]) return msg.reply(already) 13 | else { 14 | 15 | // guild[msg.guild.id] 값을 아래의 오브젝트로 저장 16 | Guild[msg.guild.id] = { 17 | leveling: true, 18 | gambling: true 19 | } 20 | let registered = new Discord.MessageEmbed() 21 | .setDescription("✅ 성공적으로 해당 길드의 데이터베이스가 생성되었습니다.") 22 | .setColor("7289DA") 23 | 24 | // Guild 저장 25 | Guild.save() 26 | msg.reply(registered) 27 | } 28 | } 29 | 30 | exports.config = { 31 | name: '서버가입', 32 | aliases: ['registerGuild'], 33 | category: ['database'], 34 | des: ['튜토봇에 이 서버만의 데이터베이스를 생성합니다.'], 35 | use: ['튜토야 서버가입'] 36 | } -------------------------------------------------------------------------------- /6화/Commands/database/settings.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js") 2 | const reload = require("self-reload-json") 3 | const guild = new reload("./json/server.json") 4 | 5 | exports.run = async(client, msg, args, prefix) => { 6 | let noperm = new Discord.MessageEmbed() 7 | .setDescription("❎ 당신은 관리자 권한이 없습니다.") 8 | .setColor("RED") 9 | if (!msg.member.hasPermission("ADMINISTRATOR")) return msg.reply(noperm) 10 | let empty = new Discord.MessageEmbed() 11 | .setTitle("⚙️ 현재 설정되어 있는 것들") 12 | .setColor("7289DA") 13 | .setDescription(`레벨링: ${guild[msg.guild.id].leveling ? "✅" : "❎"}\n도박: ${guild[msg.guild.id].gambling ? "✅" : "❎"}`) 14 | .setFooter(`기능들을 온오프 하고 싶으시면 "${prefix}설정 <기능>"으로 해주세요.`) 15 | switch (args[0]) { // args[0]에 대한 여러가지 변수가 있을때 사용함 16 | case "레벨링": // args[0] == 레벨링 일 경우 17 | let emptyGuild = new Discord.MessageEmbed() 18 | .setDescription("❎ 현재 이 길드는 데이터베이스에 저장되어 있지 않습니다.") 19 | .setFooter(`데이터베이스에 가입하고 싶으시면\n서버 관리자중"${prefix} 서버가입" 명령어를 사용해주세요.`) 20 | .setColor("RED") 21 | 22 | // guild[msg.guild.id] 가 없을 경우 23 | if (!guild[msg.guild.id]) return msg.reply(emptyGuild) 24 | 25 | // guild[msg.guild.id].leveling이 true일 경우 26 | if (guild[msg.guild.id].leveling === true) { 27 | let question = new Discord.MessageEmbed() 28 | .setDescription("정말로 이 서버에서 레벨링을 비활성화 하시겠습니까?") 29 | .setColor("7289DA") 30 | msg.reply(question).then(async message => { 31 | await message.react("✅") 32 | await message.react("❎") 33 | 34 | // 필터 생성 35 | let filterYes = (reaction, user) => reaction.emoji.name === '✅' && user.id == msg.author.id 36 | 37 | // 콜랙터 생성 38 | let collectorYes = message.createReactionCollector(filterYes, { max: 1, time: 60000 }) 39 | 40 | // 콜랙터 이벤트 생성 41 | collectorYes.on('collect', () => { // 반응을 할 경우 42 | message.delete() 43 | let success = new Discord.MessageEmbed() 44 | .setDescription("성공적으로 이 서버에 레벨링 기능을 비활성화 하였습니다.") 45 | .setColor("7289DA") 46 | msg.reply(success) 47 | 48 | // guild.leveling = false 49 | guild[msg.guild.id].leveling = false 50 | 51 | // guild 저장 52 | guild.save() 53 | }) 54 | // 콜랙터 이벤트 생성 55 | collectorYes.on('end', (_, reason) => { // 이벤트가 종료되었을 경우 56 | if (reason === "time") { // 종료된 사유가 time일 경우 57 | message.delete() 58 | let timeover = new Discord.MessageEmbed() 59 | .setDescription("시간이 초과가 되었으니 다시 시도해주세요.") 60 | .setColor("RED") 61 | msg.reply(timeover) 62 | } 63 | }) 64 | 65 | // 필터 생성 66 | let filterNo = (reaction, user) => reaction.emoji.name === '❎' && user.id === msg.author.id 67 | 68 | // 콜랙터 생성 69 | let collectorNo = message.createReactionCollector(filterNo, { max: 1, time: 60000 }) 70 | 71 | // 콜랙터 이벤트 생성 72 | collectorNo.on('collect', () => { // 반응을 할 경우 73 | message.delete() 74 | let cancel = new Discord.MessageEmbed() 75 | .setDescription("레벨링 비활성화를 취소하였습니다.") 76 | .setColor("RED") 77 | msg.reply(cancel) 78 | }) 79 | }) 80 | 81 | // guild[msg.guild.id].leveling이 false일 경우 82 | } else if (guild[msg.guild.id].leveling === false) { 83 | let question = new Discord.MessageEmbed() 84 | .setDescription("정말로 이 서버에서 레벨링을 활성화 하시겠습니까?") 85 | .setColor("7289DA") 86 | msg.reply(question).then(async message => { 87 | await message.react("✅") 88 | await message.react("❎") 89 | 90 | // 필터 생성 91 | let filterYes = (reaction, user) => reaction.emoji.name === '✅' && user.id == msg.author.id 92 | 93 | // 콜랙터 생성 94 | let collectorYes = message.createReactionCollector(filterYes, { max: 1, time: 60000 }) 95 | 96 | // 콜랙터 이벤트 생성 97 | collectorYes.on('collect', () => { // 반응을 할 경우 98 | message.delete() 99 | let success = new Discord.MessageEmbed() 100 | .setDescription("성공적으로 이 서버에 레벨링 기능을 활성화 하였습니다.") 101 | .setColor("7289DA") 102 | msg.reply(success) 103 | 104 | // guild.leveling = true 105 | guild[msg.guild.id].leveling = true 106 | 107 | // guild 저장 108 | guild.save() 109 | 110 | }) 111 | 112 | // 콜랙터 이벤트 생성 113 | collectorYes.on('end', (_, reason) => { // 이벤트가 종료할 경우 114 | if (reason === "time") { // 종료 사유가 time일 경우 115 | message.delete() 116 | let timeover = new Discord.MessageEmbed() 117 | .setDescription("시간이 초과가 되었으니 다시 시도해주세요.") 118 | .setColor("RED") 119 | msg.reply(timeover) 120 | } 121 | }) 122 | 123 | // 필터 생성 124 | let filterNo = (reaction, user) => reaction.emoji.name === '❎' && user.id === msg.author.id 125 | 126 | // 콜랙터 생성 127 | let collectorNo = message.createReactionCollector(filterNo, { max: 1, time: 60000 }) 128 | 129 | // 콜랙터 이벤트 생성 130 | collectorNo.on('collect', () => { // 반응을 할 경우 131 | message.delete() 132 | let cancel = new Discord.MessageEmbed() 133 | .setDescription("레벨링 활성화를 취소하였습니다.") 134 | .setColor("RED") 135 | msg.reply(cancel) 136 | }) 137 | }) 138 | } 139 | break; 140 | case "도박": // args[0] == 도박 일 경우 141 | /** 142 | * 밑에는 위와 설명이 같습니다. 143 | */ 144 | if (!guild[msg.guild.id]) return msg.reply(emptyGuild) 145 | if (guild[msg.guild.id].gambling === true) { 146 | let question = new Discord.MessageEmbed() 147 | .setDescription("정말로 이 서버에서 도박을 비활성화 하시겠습니까?") 148 | .setColor("7289DA") 149 | msg.reply(question).then(async message => { 150 | await message.react("✅") 151 | await message.react("❎") 152 | let filterYes = (reaction, user) => reaction.emoji.name === '✅' && user.id == msg.author.id 153 | let collectorYes = message.createReactionCollector(filterYes, { max: 1, time: 60000 }) 154 | collectorYes.on('collect', () => { 155 | message.delete() 156 | let success = new Discord.MessageEmbed() 157 | .setDescription("성공적으로 이 서버에 도박 기능을 비활성화 하였습니다.") 158 | .setColor("7289DA") 159 | msg.reply(success) 160 | guild[msg.guild.id].gambling = false 161 | guild.save() 162 | }) 163 | collectorYes.on('end', (_, reason) => { 164 | if (reason === "time") { 165 | message.delete() 166 | let timeover = new Discord.MessageEmbed() 167 | .setDescription("시간이 초과가 되었으니 다시 시도해주세요.") 168 | .setColor("RED") 169 | msg.reply(timeover) 170 | } 171 | }) 172 | let filterNo = (reaction, user) => reaction.emoji.name === '❎' && user.id === msg.author.id 173 | let collectorNo = message.createReactionCollector(filterNo, { max: 1, time: 60000 }) 174 | 175 | collectorNo.on('collect', () => { 176 | message.delete() 177 | let cancel = new Discord.MessageEmbed() 178 | .setDescription("도박 비활성화를 취소하였습니다.") 179 | .setColor("RED") 180 | msg.reply(cancel) 181 | }) 182 | }) 183 | } else if (guild[msg.guild.id].gambling === false) { 184 | let question = new Discord.MessageEmbed() 185 | .setDescription("정말로 이 서버에서 도박을 활성화 하시겠습니까?") 186 | .setColor("7289DA") 187 | msg.reply(question).then(async message => { 188 | await message.react("✅") 189 | await message.react("❎") 190 | let filterYes = (reaction, user) => reaction.emoji.name === '✅' && user.id == msg.author.id 191 | let collectorYes = message.createReactionCollector(filterYes, { max: 1, time: 60000 }) 192 | collectorYes.on('collect', () => { 193 | message.delete() 194 | let success = new Discord.MessageEmbed() 195 | .setDescription("성공적으로 이 서버에 도박 기능을 활성화 하였습니다.") 196 | .setColor("7289DA") 197 | msg.reply(success) 198 | guild[msg.guild.id].gambling = true 199 | guild.save() 200 | 201 | }) 202 | collectorYes.on('end', (_, reason) => { 203 | if (reason === "time") { 204 | message.delete() 205 | let timeover = new Discord.MessageEmbed() 206 | .setDescription("시간이 초과가 되었으니 다시 시도해주세요.") 207 | .setColor("RED") 208 | msg.reply(timeover) 209 | } 210 | }) 211 | let filterNo = (reaction, user) => reaction.emoji.name === '❎' && user.id === msg.author.id 212 | let collectorNo = message.createReactionCollector(filterNo, { max: 1, time: 60000 }) 213 | 214 | collectorNo.on('collect', () => { 215 | message.delete() 216 | let cancel = new Discord.MessageEmbed() 217 | .setDescription("도박 활성화를 취소하였습니다.") 218 | .setColor("RED") 219 | msg.reply(cancel) 220 | }) 221 | }) 222 | } 223 | break; 224 | default: 225 | msg.reply(empty) 226 | break; 227 | } 228 | 229 | } 230 | 231 | exports.config = { 232 | name: '설정', 233 | aliases: ['settings', ], 234 | category: ['database'], 235 | des: ['데이터베이스에 대한 설정을 합니다.'], 236 | use: ['튜토야 설정 <항목들>'] 237 | } -------------------------------------------------------------------------------- /6화/Commands/moderator/ban.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("차단하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.ban(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 차단 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '차단', 19 | aliases: ['ban', '벤'], 20 | category: ['moderator'], 21 | des: ['유저를 해당 서버에서 차단 시킵니다.'], 22 | use: ['튜토야 차단 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /6화/Commands/moderator/clear.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | if (!args[0]) return msg.reply("청소할 만큼의 값을 정수로 적어주세요!") 3 | if (!Number(args[0])) return msg.reply("메세지를 지울 값이 숫자가 아니면 안되요!") 4 | if (args[0] < 1) return msg.reply("메세지를 지울 값을 1보다 작게 하시면 안되요!") 5 | if (args[0] > 100) return msg.reply("메세지를 지울 값이 100보다 크면 메세지가 안지워져요!") 6 | 7 | msg.channel.bulkDelete(args[0]).then(msg.reply(`성공적으로 ${args[0]}개 만큼 메세지를 삭제하였습니다!`)) 8 | } 9 | 10 | exports.config = { 11 | name: '청소', 12 | aliases: ['clear', 'clean'], 13 | category: ['moderator'], 14 | des: ['bulkdelete'], 15 | use: ['튜토야 청소 <청소 할 메세지의 수>'] 16 | } -------------------------------------------------------------------------------- /6화/Commands/moderator/kick.js: -------------------------------------------------------------------------------- 1 | exports.run = async (client, msg, args, prefix) => { 2 | var user = msg.mentions.users.first(); 3 | if (!user) { 4 | msg.reply("추방하시기 전에 맨션을 먼저 해주세요!") 5 | } else { 6 | var member = msg.guild.member(user); 7 | if (member) { 8 | member.kick(`${msg.author.username}님의 의해 서버에서 추방됨.`).then(member => { 9 | msg.reply(`성공적으로 ${member.user.tag}님을 추방하였습니다.`) 10 | }).catch(msg.reply("해당 유저를 킥 할 권한이 없습니다.")) 11 | } else { 12 | msg.reply("이 서버에 존재하지 않은 유저입니다!") 13 | } 14 | } 15 | } 16 | 17 | exports.config = { 18 | name: '추방', 19 | aliases: ['킥', 'kick'], 20 | category: ['moderator'], 21 | des: ['유저를 강제퇴장 시킵니다.'], 22 | use: ['튜토야 추방 <유저 맨션>'] 23 | } -------------------------------------------------------------------------------- /6화/README.MD: -------------------------------------------------------------------------------- 1 | ### 잠시 튜토리얼을 보시기 전 읽어주세요. 2 | 3 | 이번 6화 튜토리얼은 json을 활용하여 레벨링, 도박기능을 만들어볼려고 합니다! 4 | 이번에 필요한 모듈은 [self-reload-json](https://www.npmjs.com/package/self-reload-json) 입니다! 5 | 6 | 이번 설명이 약간 딱딱할 수 있습니다. 7 | 그래도 자세하게 설명해놨으니 꼭 주석처리 된 부분을 읽으셔서 해당 코드들을 이해해주시면 좋을꺼 같습니다! 8 | 9 | 살짝 알고리즘 관련으로 진행하는거니 참고하여 잘 따라와주시길 바랍니다 :D 10 | 11 | 12 | ### 추가 된 파일 13 | 14 | ./Commands/database (folder) 15 |   ㄴ settings.js 16 |   ㄴ registerGuild.js 17 |   ㄴ gamble.js 18 |   ㄴ money.js 19 | 20 | ./json (folder) 21 |   ㄴ server.json 22 |   ㄴ user.json 23 | 24 | ### 수정 된 파일 25 | 26 | main(bot).js 27 | 28 | 29 | -------------------------------------------------------------------------------- /6화/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const client = new Discord.Client(); 3 | const fs = require('fs') 4 | const config = require("./config.json") 5 | const reload = require("self-reload-json") 6 | const guild = new reload("./json/server.json") 7 | const User = new reload("./json/user.json") 8 | client.on("ready", () => { 9 | console.log(`${client.user.tag} 봇에 로그인 하였습니다!`); 10 | }); 11 | client.commands = new Discord.Collection() 12 | client.aliases = new Discord.Collection() 13 | client.devs = ['570617337691111444', "1234567890"] 14 | client.category = ['bot', 'crawling', 'moderator', 'owner', 'database'] 15 | fs.readdirSync("./Commands/").forEach(dir => { 16 | const Filter = fs.readdirSync(`./Commands/${dir}`).filter(f => f.endsWith(".js")); 17 | Filter.forEach(file => { 18 | const cmd = require(`./Commands/${dir}/${file}`); 19 | client.commands.set(cmd.config.name, cmd) 20 | for (let alias of cmd.config.aliases) { 21 | client.aliases.set(alias, cmd.config.name) 22 | } 23 | }) 24 | }) 25 | 26 | 27 | function runCommand(command, msg, args, prefix) { 28 | if (client.commands.get(command) || client.aliases.get(command)) { 29 | const cmd = client.commands.get(command) || client.commands.get(client.aliases.get(command)) 30 | if (cmd) cmd.run(client, msg, args, prefix); 31 | return 32 | } 33 | } 34 | client.on("message", async msg => { 35 | const prefix = "튜토야 " 36 | if (msg.author.bot) return; 37 | if (!msg.content.startsWith(prefix)) return; 38 | let args = msg.content.slice(prefix.length).trim().split(/ +/g) 39 | let command = args.shift().toLowerCase() 40 | try { 41 | runCommand(command, msg, args, prefix) 42 | } catch (e) { 43 | console.error(e) 44 | } 45 | 46 | }) 47 | 48 | // 레벨링을 위하여 message 이벤트를 하나 더 생성함. 49 | client.on("message", async msg => { 50 | // User[msg.author.id]가 없을 경우 51 | if (!User[msg.author.id]) { 52 | User[msg.author.id] = { 53 | level: 1, 54 | money: 0, 55 | xp: 0 56 | } 57 | // User 저장 58 | User.save() 59 | } 60 | 61 | // 무작위숫자 함수 62 | function getRandomInt(min, max) { 63 | return Math.floor(Math.random() * (max - min)) + min; 64 | } 65 | 66 | // guild[msg.guild.id]가 없을 경우 return; 67 | if (!guild[msg.guild.id]) return; 68 | 69 | // guild[msg.guild.id].leveling == false일 경우 return; 70 | if (guild[msg.guild.id].leveling == false) return; 71 | 72 | // 유저의 경험치는 1,10까지 랜덤으로 획득 73 | User[msg.author.id].xp += getRandomInt(1, 10) 74 | 75 | // User 저장 76 | User.save() 77 | 78 | // 만약에 xp가 level * 75보다 클 경우 79 | if (User[msg.author.id].xp > User[msg.author.id].level * 75) { 80 | 81 | // xp = 0 82 | User[msg.author.id].xp = 0 83 | 84 | /** 85 | * level = level + 1 86 | * 87 | * level이 2였다면 3으로 오름. 88 | */ 89 | User[msg.author.id].level = User[msg.author.id].level + 1 90 | 91 | // 랜덤으로 지급할 돈을 변수로 선언 92 | let money = getRandomInt(100, 500) 93 | 94 | // money = money + money <- 위에서 선언한 money 95 | User[msg.author.id].money = User[msg.author.id].money + money 96 | let levelup = new Discord.MessageEmbed() 97 | .setDescription(`당신은 ${User[msg.author.id].level}레벨로 레벨업 하였습니다!\n레벨업 보상으로 ${money}원을 지급하였습니다.`) 98 | .setColor("7289DA") 99 | msg.reply(levelup) 100 | 101 | // User 저장 102 | User.save() 103 | } 104 | }) 105 | 106 | client.login(config.token) -------------------------------------------------------------------------------- /6화/example.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "your bot token!" 3 | } -------------------------------------------------------------------------------- /6화/json/server.json: -------------------------------------------------------------------------------- 1 | {"620871538920521728":{"leveling":true,"gambling":true}} -------------------------------------------------------------------------------- /6화/json/user.json: -------------------------------------------------------------------------------- 1 | {"708327173689442305":{"level":2,"money":443,"xp":121},"518729656749522956":{"level":4,"money":784,"xp":133},"570617337691111444":{"level":3,"money":1791,"xp":217},"578499535471378432":{"level":1,"money":0,"xp":9}} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 제스퍼 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

디스코드 자바스크립트 튜토리얼에 오신 것을 환영합니다!

2 | 3 | [![CodeFactor](https://www.codefactor.io/repository/github/ukong0324/discord-js-tutorial/badge)](https://www.codefactor.io/repository/github/ukong0324/discord-js-tutorial) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/74227fdb47374f1d81d506bf6f57a2cd)](https://app.codacy.com/manual/Ukong0324/Discord-JS-Tutorial?utm_source=github.com&utm_medium=referral&utm_content=Ukong0324/Discord-JS-Tutorial&utm_campaign=Badge_Grade_Dashboard) 4 | 5 | ## 튜토리얼 제작자 6 | 7 | **Discord: 제스퍼#0001** 8 | **Github: [UKong0324](https://github.com/Ukong0324)** 9 | 10 |

봇 개발에 쉬운 접근을 위해 제작하였으니 참고 바랍니다!

11 | 12 | ## 오타나 소스코드 지적은 언제나 환영합니다! 13 | 14 | DM이나 저를 맨션하는 것보다는 **Issues, Pull Requests**를 사용해주시면 감사합니다. 15 | 16 | **Pull Requests를 넣어주시면 제가 검토 후 "프로젝트에 도움을 주신 분들" 이라는 라인에 적어드리도록 하겠습니다.** 17 | ## 튜토리얼 방식에 대해 설명하겠습니다. 18 | 19 | ``` 20 | 1화 2화 이런식으로 폴더별로 정리하여 올릴 예정입니다. 21 | 그리고 튜토리얼을 어디까지 진행할지 고민을 많이 하고 있습니다. 22 | 하지만 제가 가능한 선에서 알려드릴 수 있는 것들은 최대한 알려드릴테니 기대해주세요! 23 | 24 | 폴더 안에 MD 파일이 따로 있다면 꼭 읽어주세요! 25 | 초보분들이 쉽게 접근하실 수 있게 이미지들을 포함하여 올릴 예정입니다. 26 | ``` 27 | 28 | Q: **튜토리얼은 어디까지 제작하시려나요?** 29 | A: **가능하면 웬만한 봇들이 가지고 있을법한 명령어들을 제작하고 이해하실 수 있도록 제작 해보려고 합니다.** 30 | 31 | Q: **혹시 지금까지 생각하신 부분은 어디까지 인가요?** 32 | A: **모듈을 활용하는 방법, 기초적인 크롤링, 데이터베이스 활용법, 뮤직 (Lavalink) 정도로 생각하고 있습니다.** 33 | 34 | ## 튜토리얼을 시작하시기 전에 잠깐 읽어주세요! 35 | 36 | 이 강의에서 초보분들을 중심으로 튜토리얼을 하는건 맞습니다. 37 | 하지만 이 강의에서는 디스코드 모듈 안에서 만들 수 있는 명령어들은 만들지 않으려고 합니다. 38 | 이외의 명령어 만드는 것에서는 많은 주석처리로 설명을 해놓을테니 잘 읽고 따라와주시면 좋을 것 같습니다. 39 | 40 | ## 이 강의는 GITHUB에도 있지만 구름 EDU에도 있습니다! 41 | 42 | [구름 EDU](https://edu.goorm.io/)에서도 강의를 하고 있으니 Github이 불편하시다면 [여기](https://edu.goorm.io/learn/lecture/20853/%EB%94%94%EC%8A%A4%EC%BD%94%EB%93%9C-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC)를 누르셔서 강의를 들으셔도 됩니다! 43 | 44 | 45 | ## 해당 프로젝트에 ⭐️을 눌러주신다면 저에게 큰 도움이 됩니다! 46 | **혹시나 튜토리얼을 같이 제작하고 싶으신 분들은 저에게 따로 디스코드 DM(PM)을 주시면 영광입니다!** 47 | [[서포트 채널]](https://discord.gg/WVKSnJr) | [[StayCute]](https://discord.gg/2UxaNp8) 48 | 49 | ## 프로젝트에 도움을 주신 분들 50 | 51 | [sannoob](https://github.com/sannoob) | [SaidBySolo](https://github.com/SaidBySolo) | [vendetta-team](https://github.com/vendetta-team) | [제로 | Brazil](https://github.com/zero734kr) | [AkiaCode](https://github.com/AkiaCode) | [Goolgae](https://github.com/Goolgae) | [MintyU](https://github.com/MintyU) | [MadeGOD](https://github.com/MadeGOD) 52 | -------------------------------------------------------------------------------- /읽어보면 좋은것들/README.md: -------------------------------------------------------------------------------- 1 | # 이 문서는 자바스크립트 개발을 하면서 궁금했던 점이나, 유용한 것들을 정리해 놓은 문서입니다 2 | 3 | ## 자바스크립트 const, let, var 차이점 4 | [LeoHeo 님의 문서](https://gist.github.com/LeoHeo/7c2a2a6dbcf80becaaa1e61e90091e5d) 5 | [bathingape 님의 문서](https://velog.io/@bathingape/JavaScript-var-let-const-%EC%B0%A8%EC%9D%B4%EC%A0%90) 6 | 7 | ## 자바스크립트 개발자라면 알아야 할 33가지의 개념 (번역본) 8 | [33concepts-of-javascript](https://velog.io/@jakeseo_me/series/33conceptsofjavascript) 9 | 10 | ## 클린한 코드 11 | 12 | [[번역판] CLEAN CODE JAVASCRIPT](https://edu.goorm.io/learn/lecture/20119/%EB%B2%88%EC%97%AD%ED%8C%90-clean-code-javascript) 13 | 14 | ## 노드 사용에 좋은 습관들 15 | - [원문](https://github.com/goldbergyoni/nodebestpractices/blob/master/README.md) 16 | - [부분 번역판](https://github.com/goldbergyoni/nodebestpractices/blob/master/README.korean.md) 17 | 18 | 대부분 사이트 쪽과 관련된 내용을 담고 있으나 필요한 부분만 참고하시면 좋습니다 19 | 20 | ## 문제 검색 21 | [Stackoverflow](https://stackoverflow.com) 22 | 23 | * 추후 더 다양한 문서들이 추가될 예정입니다 24 | --------------------------------------------------------------------------------