포켓몬 플러그인 만들기

Table of contents

이제 앞서 만든 목업 API와 연동하는 코파일럿 플러그인을 만들 차례 입니다. 기본적으로 팀즈 툴킷이 제공하는 템플릿으로 NPM 검색 플러그인을 만들고 그것의 코드를 포켓몬 API를 호출하여 동작하도록 변경하는 방식으로 진행하겠습니다.


1. 템플릿을 이용하여 프로젝트 만들기

팀즈 툴킷이 제공하는 메세지 익스텐션 템플릿으로 프로젝트를 생성합니다. 앞서 진행한 최초 프로젝트 생성하여 테스트한 내용과 유사합니다.

VS Code를 새 창으로 엽니다. 왼쪽 메뉴에서 팀즈 툴킷 아이콘을 클릭합니다. 패널에서 Create a new App 을 클릭합니다. 생성할 수 있는 팀즈 앱 종류 중에서 Message Extension을 선택하여 클릭합니다.

*******


Custom Search Results 를 선택합니다.

*******


Start with a Bot을 선택합니다.

*******


JavaScript 를 선택합니다.

*******


Default folder를 선택합니다.

*******


플러그인 프로젝트의 이름을 정해 줍니다.

*******


프로젝트가 만들어지고 새 창으로 열립니다.

*******


2. 포켓몬 플러그인으로 코드 수정

플러그인의 코드를 수정하겠습니다.

구체적으로는 세 개의 파일 내용을 수정할 것입니다.

  • searchApp.js
  • pokemonCard.js
  • manifest.json

2-1. searchApp.js 수정

searchApp.js 는 사용자가 입력한 파라미터를 전달받아 데이터 소스 (포켓몬 API)에서 검색결과를 받아 코파일럿에게 반환하는 역할을 합니다. searchApp.js 는 src 폴더 아래에 있습니다.

searchApp.js 파일을 열어 class SearchApp extends TeamsActivityHandler 코드를 찾습니다. 코드의 7번 라인입니다.

*******


이 위치 바로 위에 아래의 코드 세줄을 삽입합니다.

1
2
3
const pokemonCard = require("./adaptiveCards/pokemonCard.json");
const { Console } = require("console");
const { type } = require("os");

그러면 아래와 같이 될 것입니다.

*******


그 바로 아래에 아래의 코드 (두개의 함수) 를 삽입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function search(nameKey, myArray, returnVal){
  for (let i=0; i < myArray.length; i++) {
      if (myArray[i].name === nameKey) {
        if (returnVal){
          return myArray[i].value;
        }
        else{
          return myArray[i];
        }
      }
  }
}

function timestamp(){
  var today = new Date();
  today.setHours(today.getHours() + 9);
  return today.toISOString().replace('T', ' ').substring(0, 19);
}

그러면 아래와 같이 될 것입니다.

*******


코드에서 SearchApp 클래스를 찾아 선택합니다. 이 클래스를 통째로 지우고 교체할 것입니다.

*******


SearchApp 클래스를 아래의 코드로 교체합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
class SearchApp extends TeamsActivityHandler {
  constructor() {
    super();
  }

  // Message extension Code
  // Search.
  async handleTeamsMessagingExtensionQuery(context, query) {
    //const searchQuery = query.parameters[0].value;
    console.log(search('poke_name', query.parameters, false));
    console.log(search('poke_type', query.parameters, false));
    console.log(timestamp());
    
    let poke_name = "", poke_type = "";
    if (search('poke_name', query.parameters, false)){
      poke_name = search('poke_name', query.parameters, false).value;
    }
    if (search('poke_type', query.parameters, false)){
      poke_type = search('poke_type', query.parameters, false).value;
    }

    //주소를 유의하십시오
    const response = await axios.get(
      `https://pogokrapi.azurewebsites.net/api/pogo?${querystring.stringify({
        name: poke_name,
        type: poke_type,
      })}`
    );

    const attachments = [];
    response.data.pokemons.forEach((pokemon) => {
      const evols = [];
      if (pokemon.prev_evolution){
        pokemon.prev_evolution.forEach((evol) => {
          evols.push(evol);
        })
      }
      evols.push({
        "num":pokemon.num,
        "name":pokemon.name
      });
      if (pokemon.next_evolution){
        pokemon.next_evolution.forEach((evol) => {
          evols.push(evol);
        })
      }

      let temp_evolimg1 = "", temp_evolimg2 = "", temp_evolimg3 = "", temp_evolimg4 = ""
      let temp_evolname1 = "", temp_evolname2 = "", temp_evolname3 = "", temp_evolname4 = "";
      if (evols[0]) {
        temp_evolimg1 = "http://www.serebii.net/pokemongo/pokemon/" + evols[0].num + ".png";
        temp_evolname1 = evols[0].name;
      }
      if (evols[1]) {
        temp_evolimg2 = "http://www.serebii.net/pokemongo/pokemon/" + evols[1].num + ".png";
        temp_evolname2 = evols[1].name;
      }
      if (evols[2]) {
        temp_evolimg3 = "http://www.serebii.net/pokemongo/pokemon/" + evols[2].num + ".png";
        temp_evolname3 = evols[2].name;
      }
      if (evols[3]) {
        temp_evolimg4 = "http://www.serebii.net/pokemongo/pokemon/" + evols[3].num + ".png";
        temp_evolname4 = evols[3].name;
      }

      const template = new ACData.Template(pokemonCard);
      const card = template.expand({
        $root: {
          num: pokemon.num,
          name: pokemon.name,
          img: pokemon.img,
          type: pokemon.type,
          height: pokemon.height,
          weight: pokemon.weight,
          candy: pokemon.candy,
          weaknesses: pokemon.weaknesses.join(", "),
          evolimg1: temp_evolimg1,
          evolname1: temp_evolname1,
          evolimg2: temp_evolimg2,
          evolname2: temp_evolname2,
          evolimg3: temp_evolimg3,
          evolname3: temp_evolname3,
          evolimg4: temp_evolimg4,
          evolname4: temp_evolname4,
        },
      });
      const preview = CardFactory.heroCard(pokemon.name);
      const attachment = { ...CardFactory.adaptiveCard(card), preview };
      attachments.push(attachment);
    });

    return {
      composeExtension: {
        type: "result",
        attachmentLayout: "list",
        attachments: attachments,
      },
    };
  }
}

2-2. pokemonCard.json 추가

이제 커스텀 메세지 카드 즉, 어댑티브 카드를 만들어 줄 차례 입니다.

src 폴더 아래에 있는 adaptiveCards 폴더를 선택한 채로 파일추가 버튼을 클릭합니다. 파일 이름은 pokemonCard.json 으로 입력해 주십시오.

*******


json 파일의 내용을 채워줍니다. 채울 코드 내용은 여기 (https://lanslote.github.io/copilot/plugin-MES-HOL/42/) 에 있습니다.

*******


2-3. 플러그인 아이콘 파일 준비

플러그인이 적절한 아이콘을 가지게 되면, 사용자 경험에 많은 도움이 됩니다. 다음 경로에서 두개의 이미지 파일을 다운로드 하여 프로젝트의 appPakage 폴더에 추가해 주십시오.

  • https://lanslote.github.io/copilot/plugin-ME-HOL/assets/40/color_ball.png
  • https://lanslote.github.io/copilot/plugin-ME-HOL/assets/40/outline_ball.png

*******


*******


2-3. manifest.json 수정

이제 매니페스트 파일을 수정할 차례 입니다. 매니페스트 파일은 앱의 중요정보를 가지고 있는 파일로 앱 배포의 핵심이라고 할 수 있습니다. appPakage 폴더 아래에 있는 manafest.json 파일을 열어 아래 항목들을 찾아 변경해 주십시오.

1
2
3
4
"icons": {
  "color": "color_ball.png",
  "outline": "outline_ball.png"
},
1
2
3
4
"name": {
  "short": "PokemonME${{APP_NAME_SUFFIX}}",
  "full": "포켓몬 정보 조회 코파일럿 플러그인"
},
1
2
3
4
"description": {
  "short": "포켓몬 정보를 이름과 타입으로 검색합니다.",
  "full": "포켓몬 정보를 이름과 타입으로 검색합니다. \n예) 꼬부기라는 이름의 포켓몬 정보를 찾아줘 \n예) 전기 타입의 포켓몬 정보를 찾아줘"
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"commands": [
  {
    "id": "nameSearch",
    "context": [
        "compose",
        "commandBox"
    ],
    "description": "이름으로 포켓몬을 검색",
    "title": "포켓몬 이름",
    "type": "query",
    "semanticDescription": "이 커맨드는 제공된 이름으로 찾은 포켓몬의 정보를 받습니다.",
    "parameters": [
        {
            "name": "poke_name",
            "title": "포켓몬 이름",
            "description": "검색할 포켓몬의 이름",
            "inputType": "text",
            "semanticDescription": "이 매개변수는 검색할 포켓몬을 식별하기 위한 것입니다. 사용자는 찾고자 하는 포켓몬의 이름을 이 매개변수의 값으로 제공해야 합니다."
        }
    ]
  },
  {
    "id": "typeSearch",
    "context": [
        "compose",
        "commandBox"
    ],
    "description": "타입으로 포켓몬을 검색",
    "title": "포켓몬 타입",
    "type": "query",
    "semanticDescription": "이 커맨드는 제공된 타입으로 찾은 포켓몬의 정보를 받습니다. 예를 들어 '전기 타입인 포켓몬 찾아줘'라고 사용자가 말하면 검색을 원하는 타입이 '전기'입니다",
    "parameters": [
        {
            "name": "poke_type",
            "title": "포켓몬 타입",
            "description": "검색할 포켓몬의 타입",
            "inputType": "text",
            "semanticDescription": "이 매개변수는 검색할 포켓몬을 식별하기 위한 것입니다. 사용자는 찾고자 하는 포켓몬의 타입을 이 매개변수의 값으로 제공해야 합니다."
        }
    ]
  }
]

3. 포켓몬 플러그인 테스트

플러그인의 코드 수정이 완료되었습니다. 이제 로컬 환경에서 실행해서 정상동작하는지 확인해 보겠습니다.

디버깅을 하기 전에 디버그 환경 설정을 해주어야 합니다. VS Code 왼쪽 메뉴에서 Run and Debug 아이콘을 클릭하여 디버깅 패널을 엽니다. 패널 위쪽에 Debug in Teams (Edge) 혹은 Debug in Teams (Chrome) 을 선택합니다. 그리고 F5 키를 눌러 디버깅을 시작합니다.

*******


프로젝트가 빌드되고 관련 서비스의 프로비저닝이 완료되면 디버깅 실행을 위해 웹브라우저가 뜹니다. (로그인을 요구할 수도 있습니다.)

*******


로그인한 브라우저에서 팀즈가 실행되고 디버깅할 플러그인 앱이 추가를 기다리고 있습니다. 추가 버튼을 클릭합니다.

*******


배포된 앱이 메세지 익스텐션으로 정상동작하는지 확인하는 것이 먼저 입니다. 팀즈 챗의 대화입력 창에서 + 를 클릭하여 메세지 익스텐션 앱의 목록을 부릅니다. 디버깅할 포켓몬 메세지 익스텐션을 찾아 클릭합니다.

*******


포켓몬 메세지 익스텐션은 이름과 타입으로 검색할 수 있습니다. 간단하게 포켓몬 이름을 검색해서 잘 동작하는지 확인합니다. 검색된 포켓몬의 이름 하나를 클릭해 봅니다.

*******


개별 포켓몬의 상세정보를 보여주는 어댑티브 카드가 잘 동작하는지 확인해 봅니다.

*******


메세지 익스텐션으로 정상동작하는 것이 확인되었으니 코파일럿 플러그인으로도 정상동작하는지 확인할 차례 입니다. 팀즈의 M365 Chat 에서 플러그인 목록을 열어 나의 플러그인을 찾아 활성화 시켜줍니다.

*******


플러그인을 사용하는 프롬프트를 적어줍니다. 저는 플러그인이름, 피카츄 포켓몬 찾아줘 라고 입력했습니다.

*******


지금 디버깅 실행중이므로, VS Code 에서 실시간으로 어떤 파라미터가 전달되는지 터미널 창을 통해 볼 수 있습니다.

*******


코파일럿 플러그인으로도 잘 동작하는 것을 확인해 봅니다.

*******


Table of contents