Puppeteer, Electron을 사용해서 - 매크로만들기

그냥차차·2024년 6월 24일
0

프로젝트

목록 보기
3/4
post-thumbnail

매크로 깃허브

0. 결과물

  • 컴퓨터가 켜있고, 데스크탑앱이 실행중이라면 매크로가 매일 오전 12:01에 자동으로 실행함으로 이제 어이없게 챌린지를 실패할일 없음!

1. 챌린지 매크로

ㄱ. 프로젝트 소개

  • 100일간 강의를 들으면 100% 환급을 해주는 강의를 듣던중 까먹고 하루를 못들었음
  • 이렇게 하다가 99일째에도 까먹고 못들을꺼 같아서 매크로를 만들기로 결심
  • Electron, Express, Puppeteer 사용하여 매일 오전 12:01분에 자동으로 강의영상을 틀게끔 매크로를 돌려보자

ㄴ. 사용스택

  • Electron : electron을 사용하여 매크로 앱을 만들것
  • Express : 매크로 앱이 실행되면 express로 로컬호스트 4000번 포트를 실행하여 간단하게 서버를 구축할것
  • Puppeteer : Dom을 직접조작하여 24간마다 홈페이지에 접근하여 로그인을하고 강의영상을 클릭할것

2. 프로젝트 준비

ㄱ. 프로젝트

  • 프로젝트 폴더를 하나만들고 npm init -y 하고 필요한 Electron, Express, Puppeteer 등을 설치

ㄴ. 설치

  • Electron : npm install electron --save-dev
  • Express : npm install express
  • Puppeteer : npm install puppeteer

3. Electron & Express

ㄱ. 기본설정

  • npm start 하면 Electron 앱이 실행되게 package.json의 scripts를 수정해줌
"scripts": {
  "start": "electron ."
}
  • main.js파일을 만들고 Electon가 정상작동하는지 테스트해볼것
// main.js 
const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});
  • Electon앱이 실행될때 보여줄 index.html파일을 만들기
// index.html
<!DOCTYPE html>
<html>
  <head>
    <title>My Electron App</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <p>This is my first Electron app.</p>
  </body>
</html>
  • 기본설정을 마치고npm start를 터미널에 입력하면 아래 이미지처럼 잘됨

ㄴ.Html 꾸미기

  • Html의 기본적인 요소(제목 ,Input, 현재시간, 로그인버튼)를 꾸며줌
  • 로그인 완료후에도 보여질 화면도 꾸며줌

ㄴ.로그인

  • npm start를 하면 Electron앱 실행과 동시에 Express가 실행되야함
// main.js
const { app, BrowserWindow } = require('electron');
const express = require('express');
const puppeteer = require('puppeteer');

const serverApp = express();
const port = 4000;

serverApp.get('/', (req, res) => {
  res.send('Hello, express!');
});


function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadURL(`http://localhost:${port}`);
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});
  • Html에 입력한 input의 Id와 Password값을 받아야함
  • index. html에 스크립트 파일을 설정해주고 form 태그 id를 loginForm로 정의함
// html
document
  .getElementById("loginForm")
  .addEventListener("submit", async function (event) {
    event.preventDefault();

    const form = event.target;
    const formData = new FormData(form);

    const response = await fetch(form.action, {
      method: form.method,
      body: new URLSearchParams(formData),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
    });

    if (response.ok) {
      console.log("response", response);
      alert("로그인 완료");
      document.getElementById("content").classList.remove("hidden");
      document.getElementById("loginForm").classList.add("hidden");
      document.getElementById("loginMessage").classList.remove("hidden");
    } else {
      alert("로그인 실패");
    }
  });
  • index.html에서 보내준 아이디와 비밀번호를 let변수에 저장해둠
//main.js 
let username
let password
serverApp.post('/login', (req, res) => {
   username = req.body.username;
   password = req.body.password;
  console.log(`아이디: ${username}, 비밀번호: ${password}`);
  res.send('로그인 정보가 전송되었습니다.');
});

ㄷ. 챌린지 강의 듣기

  • 이제 로그인을 완료하면 Input form이 없어지게 styles.css를 수정하고
  • 아래 이미지의 챌린지 강의듣기 버튼을 매일 오전 12:01일때 main.js로 play라는 값을 보내서 Puppeteer가 작동하게할것
  • play.js파일을 만들고 아래 코드를 넣어줌
document.getElementById("play").addEventListener("click", async function () {
  const sendPlayRequest = async () => {
    const response = await fetch("/play", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({ action: "play" }),
    });

    if (response.ok) {
      alert("챌린지 강의 듣기 시작");
    } else {
      alert("챌린지 강의 듣기 실패");
    }
  };

  await sendPlayRequest();

  // 특정 시간이 될 때까지 체크하는 함수
  const checkTimeAndSendRequest = () => {
    const now = new Date();
    const hours = now.getHours();
    const minutes = now.getMinutes();

    // 오전 12시 1분에 요청 보내기
    if (hours === 0 && minutes === 1) {
      sendPlayRequest();
      clearInterval(intervalId); // 요청을 보낸 후 interval을 중지합니다.
    }
  };

  // 1초마다 현재 시간을 체크합니다.
  const intervalId = setInterval(checkTimeAndSendRequest, 1000);
});
  • setTimeout으로 1분마다 play를 보내봤는데 정상 작동하였음

4. Puppeteer

ㄱ. puppeteer.js

  • 이제 main.js에 request body값에 play라는 값이 찍히면 puppeteer을 이용하여 강의를 재생할것
  • puppeteer를 main.js에서 불러오고 인자로 유저의 아이디와 패스워드를 넘겨주고 아래와 같이 puppeteer.js를 작성하면
  • 아래코드를 간단히 설명하자면
//puppeteer.js
const puppeteer = require("puppeteer");

async function performLoginAndAction(username, password) {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();

  page.on("dialog", async (dialog) => {
    await dialog.accept();
  });

  await page.goto("url");

  console.log("0");
  await page.waitForSelector("#input_id");

  //로그인
  await page.evaluate(
    (username, password) => {
      const idInput = document.querySelector("#input_id");
      const pwInput = document.querySelector("#input_pw");
      const loginBtn = document.querySelector("#bt_login");
      if (idInput) {
        idInput.value = username;
      }
      if (pwInput) {
        pwInput.value = password;
      }
      if (idInput || pwInput) {
        loginBtn.click();
      }
    },
    username,
    password
  );

  await page.waitForNavigation();

  await page.goto("url");

  await page.waitForSelector(".box");

// 강의 선택
  await page.evaluate(() => {
    const btn = document.querySelector(".box");

    if (btn) {
      btn.click();
    }
  });

  await page.waitForSelector(".lec_box button");

  // 강의 플레이
  await page.evaluate(() => {
    const btn4 = document.querySelector(".lec_box");
    if (btn4) {
      btn4.querySelector("button").click();
    }
  });
}

module.exports = performLoginAndAction;

5. build

  • Electron 앱을 빌드하기 위해 npm install --save-dev electron-builder하고
  • package.json을 수정해줌
{
  "name": "siwonscholl_macro",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "bin": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "electron .",
    "build": "electron-builder"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.20.2",
    "express": "^4.19.2",
    "puppeteer": "^22.12.0"
  },
  "devDependencies": {
    "electron": "^31.0.2",
    "electron-builder": "^24.3.0"
  },
  "build": {
    "appId": "com.example.siwonscholl_macro",
    "files": [
      "**/*"
    ],
    "directories": {
      "buildResources": "build"
    }
  }
}
  • 터미널에 npm run build를 입력하면 같은 디렉토리에 아래와같이 데스크탑앱이 빌드됨!

6. 화면잠금 모드일때 실행을 하지않는 문제

  • 맥을 사용중인데 화면잠금 모드일때는 작동하지않아서 방법을 두가지 생각해봄
    • 스크립트만 떼어낸후 화면모드 일때 puppeteer만 작동하게끔 = > 이런게 가능하다고해서 시도
    • 컴퓨터를 일정시간에 깨우기 = > 이건 화면잠금모드일때 비밀번호를 입력을 어떻게해야할지..

ㄱ. LaunchAgents

  • 터미널에 cd ~/Library/LaunchAgents/ 쳐서 해당 경로로 들어간뒤
  • 터미널에 nano com.example.electronapp.plist 쳐서 해당파일을 만들고
    • 아래의 코드를 넣어둠

ㄴ. puppeteer.js

  • 실행할 puppeteer 파일을 원하는 경로에 넣고 수동으로 동작하는지 테스트해보기

ㄷ. 명령어

  • ㄱ. 아래명령어로 파일의 권한을 설정해줌

    • chmod 666 "경로/파일명"
    • plutil ~/Library/LaunchAgents/com.example.electronapp.plist
  • ㄴ 로그 파일 작성 권한 확인

    • chmod 666 /경로/파일명
  • ㄷ.에이전트 언로드 및 재로드

    • sudo launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.example.electronapp.plist
    • sudo launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.electronapp.plist
  • ㄹ. 로그파일을 확인

    • log show --predicate 'process == "launchd"' --info --debug | grep com.example.electronapp
profile
개발작

0개의 댓글