【Salesforce初心者】ApexでTrailheadから情報を取得する方法


【Salesforce初心者】ApexでTrailheadから情報を取得する方法
最近僕みたいに、社内でTrailheadを利用して、Salesforceの勉強を進めている人が多くなっている(ような気がします)。
ので、皆さんがどのようにTrailhead上で勉強しているかをトラッキングできるような仕組みを考えてみようと思いました。
詳しいことはさておき、まずはTrailheadから情報を取得する方法を試してみました。

ゴール:
ユーザのプロファイルから、以下の情報を取得:
・バッジ数
・ポイント
・トレイル数
・最近獲得したバッジ

*ここに表示されている情報を取得したい

TrailheadのAPIを使えばなんとかなるじゃないかと最初思っていたが、探してみた結果、APIが公開されていないようなので、htmlのrawソースを手動でパースしないと行けないみたいです。
それでゴールを達成するため、プロセスを以下のように分解しました:
①.ユーザプロファイルのHttpレスポンスを取得
②.取得したRaw状態のソースをパースする
まずは①について、Httpクラスを使えば実現可能。

public String getProfileContent(String profileUrl){
    // HTTP接続に必要名HTTP、HTTPRequestクラスを初期化する
    HTTP h = new HTTP();
    HTTPRequest req = new HTTPRequest();

    // 指定したURLにGETメソッドで接続する
    //String url= 'https://trailhead.salesforce.com/ja/users/profiles/00550000006c0FTAAY';
    req.setEndpoint(profileUrl);
    req.setMethod('GET');

    // 実際に接続を行う
    HTTPResponse res = h.send(req);

    // HTTP Bodyを取得する
    return res.getBody();
    }  

取得したBodyはこのような感じです。

<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
<script type="text/javascript">window.NREUM||(NREUM={});NREUM.info={"beacon":"bam.nr-data.net","errorBeacon":"bam.nr-data.net","licenseKey":"caf0a1fbb3","applicationID":"31152379","transactionName":"Jl1ZFkZWX1gEFB9CSwBARE1ES1xSCApVRBcWWlgV","queueTime":1,"applicationTime":880,"agent":""}</script>
<script type="text/javascript">(window.NREUM||(NREUM={})).loader_config={xpid:"VQUBVlRRABADVVBXBQIAXw=="};window.NREUM||(NREUM={}),__nr_require=function(t,n,e){function r(e){if(!n[e]){var o=n[e]={exports:{}};t[e][0].call(o.exports,function(n){var o=t[e][1][n];return r(o||n)},o,o.exports)}return n[e].exports}if("function"==typeof __nr_require)return __nr_require;for(var o=0;o<e.length;o++)r(e[o]);return r}({1:[function(t,n,e){function r(t){try{s.console&&console.log(t)}catch(n){}}var o,i=t("ee"),a=t(15),s={};try{o=localStorage.getItem("__nr_flags").split(","),console&&"function"==typeof console.log&&(s.console=!0,o.indexOf("dev")!==-1&&(s.dev=!0),o.indexOf("nr_dev")!==-1&&(s.nrDev=!0))}catch(c){}s.nrDev&&i.on("internal-error",function(t){r(t.stack)}),s.dev&&i.on("fn-err",function(t,n,e){r(e.stack)}),s.dev&&(r("NR AGENT IN DEVELOPMENT MODE"),r("flags: "+a(s,function(t,n){return t}).join(", ")))},{}],2:[function(t,n,e){function r(t,n,e,r,o){try{d?d-=1:i("err",[o||new UncaughtException(t,n,e)])}catch(s){try{i("ierr",[s,c.now(),!0])}catch(u){}}return"function"==typeof f&&f.apply(this,a(arguments))}function UncaughtException(t,n,e){this.message=t||"Uncaught error with no additional 
...

ちらっと眺めると、抽出したい値がこの辺にあります

</div>
<div class='span9'>
<div class='row-fluid'>
<div class='span4'>
<div class='panel panel--s tally-panel'>
<div class='panel-heading'>
<h3 class='panel-heading__title'>
バッジ
</h3>
</div>
<div class='panel-body text-center'>
<h4 class='th-profile-title th-profile-title--green leading-marg-m'>
19
</h4>
</div>
</div>

</div>
<div class='span4'>
<div class='panel panel--s tally-panel'>
<div class='panel-heading'>
<h3 class='panel-heading__title'>
ポイント
</h3>
</div>
<div class='panel-body text-center'>
<h4 class='th-profile-title th-profile-title--green leading-marg-m'>
30,125
</h4>
</div>
</div>

</div>
<div class='span4'>
<div class='panel panel--s tally-panel'>
<div class='panel-heading'>
<h3 class='panel-heading__title'>
トレイル
</h3>
</div>
<div class='panel-body text-center'>
<h4 class='th-profile-title th-profile-title--green leading-marg-m'>
0
</h4>
</div>
</div>

文字列をきれいに整形・抽出するのに、Linuxだとawkとかsedなどのコマンドがあると思いますが、Apexだと、Stringのクラスを使えば良いです。
今回文字列を弄るのに使ったメソッドは以下です:
String.substringAfter(‘この文字列の後の部分を抽出’);
String.substringBefore(‘この文字列の前の部分を抽出’);
String.stripHtmlTags(); //htmlのタグ部分を外す
String.trim(); //前後のブランクを取り外す

Stringクラスの使い方は:
https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_string.htm?search_text=String

方法は他にもあると思いますが、僕の場合は以下です:

まずは、まずはraw状態で大量なhtmlコードを、必要な部分だけ抽出して、きれいにします(タグとかを外す)。

private String getNeededPart(String plainHtml){ 
        String body1 = plainHtml;
        String body2 = body1.substringAfter('div class=\'span9\'');
        String body3 = body2.substringBefore('panel panel--expandable panel--wrap');
        String part = body3.stripHtmlTags();
        return part;
    }

バッジ数を抽出

    private String getlatestBatch(String plainHtml){
        String body1 = plainHtml;
        String body2 = body1.substringAfter('badgesVisibilityFilter');
        String body3 = body2.substringBefore('バッジを獲得しました');
        String body4 = body3.substringAfter('Trailhead で');
        String latestBatch = body4.trim();
        return latestBatch;
    }

似たような方法で、ポイントやトレイル数も取得可能

    private String getscore(String part){
        String score_before = part.substringAfter('ポイント');
        String score_after = score_before.substringBefore('トレイル');
        String score = score_after.trim();
        return score;
    }
    private String getTrailNumber(String part){
        String trail_before = part.substringAfter('トレイル');
        String trailNo = trail_before.trim();
        return trailNo;
    }    

最近取得したバッジはjavascript内のソースにありますが、ちょっと工夫すれば同じく取得できます

    private String getlatestBatch(String plainHtml){
        String body1 = plainHtml;
        String body2 = body1.substringAfter('badgesVisibilityFilter');
        String body3 = body2.substringBefore('バッジを獲得しました');
        String body4 = body3.substringAfter('Trailhead で');
        String latestBatch = body4.trim();
        return latestBatch;
    }

上記で取得した値をList形式で出力してみれば、上手く取れていることがわかります。

    private List<String> createInfoList(String badgeNo, String score, String trailNo, String latestBatch){
        List<String> infoList = new List<String>();
        infoList.add(badgeNo);
        infoList.add(score);
        infoList.add(trailNo);
        infoList.add(latestBatch);
        return infoList;
    }

infoListをSystem.debugで画面に出すと:

ここまで来れば、ユーザProfileのUrlから、バッジ数やポイントなどの情報を取得することが可能になったと思います。
ちょっと雑な書き方かもしれないですが、Trailheadみたいな外部サイトから情報を取得する方法の紹介です。