【ネタ】Trailheadの取得バッジを一覧化したCSVを出力する（Bookmarklet）

「Trailheadの取得バッジを一覧化したいな」と思うことがあるでしょうか？

私は社内でSalesforceの学習進捗の管理を実施しており、社内で働くSalesforceエンジニアの皆さんに学習進捗をExcelに記録してもらっています。（[TrailTracker](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000EFpAtUAL)を使えよと自分でも思いますが、ライセンスが足りないのです）
しかし、まとめるのが大変であったり、ポイントや取得日に間違いがあったり、ポイントや取得日はログインしないと見えなかったりと問題が山積みでした。
何か対策がないかを考えたところ、[Top Trailblazers](https://toptrailblazers.com/)の「Push Badges to TT」にヒントをもらい、取得バッジの一覧化をBookmarkletでできるようにしました。

## 最終的なコード
先に出来上がったコードを示します。
下記をブックマークに登録してからTrailblazer.meのプロファイルページ（English）でブックマークを押すと、取得バッジの一覧をCSVでダウンロードできます。（取得バッジの量によっては時間がかかります）
```javascript:最終的なコード
javascript:(
    function(){
        function d(a){
            return new Promise(b=>setTimeout(b,1E3*a))
        }
        (
            async function(){
                if("en"==document.querySelector("wes-footer").shadowRoot.querySelector("lwc-wes-select").shadowRoot.querySelector("select").value){
                    for(var a=document.querySelector("tbme-profile-badges").shadowRoot.querySelector("lwc-tbui-card"),b=a.querySelector("lwc-tbui-card-footer-link").shadowRoot.querySelector("lwc-tds-button");;){
                        if("Show More"==b.querySelector("span").innerText)b.click();
                        else break;
                        await d(3)
                    }
                    b=[];
                    a=a.querySelectorAll("lwc-tbui-badge");
                    for(let c=0;c<a.length;c++){
                        let e=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelector("img").alt,f=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[2],g=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[0],h=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[1],k=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[1].innerText,l=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelector("img").src;
                        b.push(['"'+e+'"','"'+f+"-"+g+"-"+h+'"','"'+k.replace(",","")+'"','"'+l+'"'])
                    }
                    b=b.map(c=>c.join(",")).join("\n");
                    a=new Uint8Array([239,187,191]);
                    b=new Blob([a,b],{type:"text/csv"});
                    a=document.createElement("a");
                    a.download="TrailblazerMeBadges.csv";
                    a.href=window.URL.createObjectURL(b);document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a)
                }else alert("Please change the language setting to English")
            }
        )()
    }
)();
```

## 計画

#### 取得するデータ
1. バッジ名（英名）
2. 取得日
3. ポイント
4. バッジ画像のURL

#### 処理の流れ
1. 英語表記か確認する
2. バッジ一覧のボタン（Show More）を連打する
3. 連打が終わってからバッジ一覧を取得する
4. 取得したバッジ一覧をCSV用に整形する
5. ダウンロードする

#### 問題点
1. Javascriptは得意ではない
→ 勉強すればOK
2. shadowRootがあるため要素の一発取得を段階的にする必要がある
→ 親要素を取得してからshadowRootを指定することで辿れる
3. ボタン（Show More）を押してから画面に表示されるまでタイムラグがある
→ ボタンを押してから一時停止する

## 実装
計画を基に実装を進めていきます。（紆余曲折します）

#### テンプレート
```javascript:Template.js
javascript:(
    function(){
        async function main(){
            // 実装部分
        }
        main();
    }
)();
```

#### 英語表記か確認する
Trailblazer.meの言語設定はフッターの言語選択の値を取れば良いみたいです。

![Javascript_Bookmarklet_for_Trailhead_言語設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/75f8a5f4-bfef-ae40-53f3-bc1b624223f0.png)

この値を取りに行くためには、
```javascript
// フッターを取得して...
let footer  = document.querySelector('wes-footer').shadowRoot;
// 選択リストを取得して...
let selects = footer.querySelector('lwc-wes-select');
// 現在の選択リスト値を取得して...
let select  = selects.shadowRoot.querySelector('select');
// 現在の選択リスト値の値を取得!!
const lang  = select.value
```
のような形になります。
このvalueの値が現在の設定言語を表していて、下記の値と言語の関係があります。

|value|言語||value|言語||value|言語|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|da|Dansk||it|Italiano||de|Deutsch|
|zh-CN|简体中文||ko|한국어||nl-NL|Nederlands|
|pt-BR|Português (Brasil)||sv|Svenska||th|ภาษาไทย|
|no|Norsk||es-MX|Español (LATAM)||fr|Français|
|ru|Русский||ja|日本語||es|Español|
|fi|Suomi||en|English||zh-TW|繁體中文|

今回はEnglishを指定するため「en」を使用して、他の言語の場合にはアラートを表示するように設定します。
```javascript
if( lang == 'en' ){
    // 実装部分
} else {
    alert( 'Please change the language setting to English' );
}
```
これらをテンプレートの実装部分に追記します。
```javascript:Template.js
javascript:(
    function(){
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                // 実装部分
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

#### バッジ一覧のボタン（Show More）を連打する
バッジ一覧のボタンは下記のカードのフッターに配置されています。

![Javascript_Bookmarklet_for_Trailhead_バッジ一覧ボタン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/2024ae03-bf02-53e6-047f-9cf50ab467b4.png)

ボタンの探索をするためには、
```javascript
// カードを取得して...
let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
// フッターを取得して...
let link   = card.querySelector('lwc-tbui-card-footer-link');
// ボタンを取得!!
let button = link.shadowRoot.querySelector('lwc-tds-button');
```
のような形になります。
ボタンを連打するためには、無限ループを利用して
```javascript
while( true ){
    if( /** ボタンを押す条件 **/ ){
        button.click();
    } else {
        break;
    }
}
```
としておきます。
ボタンを押す条件に関しては、バッジが全て表示された際には「Show More」の表示から切り替わることを利用して、
```javascript
// ボタンの中のテキストを確認
button.querySelector('span').innerText == 'Show More'
```
として設定できます。
これらをテンプレートの実装部分に追記します。<font color="Red">（注意：この段階では実行しない）</font>
```javascript:Template.js
javascript:(
    function(){
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                }
                // 実装部分
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

#### 問題が発生
上記のコードを実行してみると、ボタンは連打できますがデータの表示が間に合わないという問題が発生し、表示されていないタイミングでもボタンを連打しまくり、結果としてPCが大暴走を始めます。
そのため、簡単な一時停止機能を下記のように付加しておきます。
```javascript:Template.js
javascript:(
    function(){
        function sleep( sec ){
            return new Promise( resolve => setTimeout( resolve , sec * 1000 ) );
        }
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                    await sleep( 3 );
                }
                // 実装部分
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```
一時停止の時間は大凡の時間で**3秒**に決めていますが、ネットワーク等の環境によってはもっと短い設定でも問題はないと思います。

#### 連打が終わってからバッジ一覧を取得する
バッジ一覧はボタンが配置されていたカードに複数のデータとして表示されているので、
```javascript
let badges = card.querySelectorAll('lwc-tbui-badge');
```
として取得しておき、繰り返し処理で中身を探索するようにします。
取得するデータに関しては、

1. バッジ名（英名）は画像のalt属性
2. 取得日は右上部のボタンの中身の上部
3. ポイントは右上部のボタンの中身の下部
4. バッジ画像のURLは画像のsrc属性

から取れます。（英語環境では取得日が「mm/dd/yyyy」の形式になっています）
```javascript
let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1]
let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
```
これらを整形した上で配列に追加していくことを踏まえて、
```javascript
let badgeList = [];
let badges    = card.querySelectorAll('lwc-tbui-badge');
for( let i = 0 ; i < badges.length ; i++ ){
    let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
    let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
    let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
    let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
    let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1].innerText
    let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
    badgeList.push([
        '"' + getName + '"',
        '"' + getYear + '-' + getMonth + '-' + getDay + '"',
        '"' + getPoint.replace( ',' , '' ) + '"' ,
        '"' + getImage + '"'
    ]);
}
```
として設定します。
これらをテンプレートの実装部分に追記します。
```javascript:Template.js
javascript:(
    function(){
        function sleep( sec ){
            return new Promise( resolve => setTimeout( resolve , sec * 1000 ) );
        }
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                    await sleep( 3 );
                }
                let badgeList = [];
                let badges    = card.querySelectorAll('lwc-tbui-badge');
                for( let i = 0 ; i < badges.length ; i++ ){
                    let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
                    let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
                    let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
                    let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
                    let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1].innerText
                    let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
                    badgeList.push([
                        '"' + getName + '"',
                        '"' + getYear + '-' + getMonth + '-' + getDay + '"',
                        '"' + getPoint.replace( ',' , '' ) + '"' ,
                        '"' + getImage + '"'
                    ]);
                }
                // 実装部分
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

#### 取得したバッジ一覧をCSV用に整形する
CSVは区切り文字が`,`で改行が`\n`で作成できるため、
```javascript
let data = badgeList.map( ( badge ) => badge.join( ',' ) ).join( '\n' );
```
のような形で整形できます。
これをテンプレートの実装部分に追記します。
```javascript:Template.js
javascript:(
    function(){
        function sleep( sec ){
            return new Promise( resolve => setTimeout( resolve , sec * 1000 ) );
        }
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                    await sleep( 3 );
                }
                let badgeList = [];
                let badges    = card.querySelectorAll('lwc-tbui-badge');
                for( let i = 0 ; i < badges.length ; i++ ){
                    let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
                    let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
                    let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
                    let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
                    let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1].innerText
                    let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
                    badgeList.push([
                        '"' + getName + '"',
                        '"' + getYear + '-' + getMonth + '-' + getDay + '"',
                        '"' + getPoint.replace( ',' , '' ) + '"' ,
                        '"' + getImage + '"'
                    ]);
                }
                let data = badgeList.map( ( badge ) => badge.join( ',' ) ).join( '\n' );
                // 実装部分
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

#### ダウンロードする
ダウンロードする前にファイルのBOMとBlobを設定する必要があり、
```javascript
let bom  = new Uint8Array( [ 0xEF , 0xBB , 0xBF ] );
let blob = new Blob( [ bom , data ] , { type : 'text/csv' } );
```
と設定しておきます。（UTF-8でCSVであることを指定しています）
ダウンロードはWebページ上にファイルをダウンロードするリンクを仮置きした上でclickイベントを発火させれば良いので、
```javascript
let dammyLink      = document.createElement( 'a' );
dammyLink.download = 'TrailblazerMeBadges.csv';
dammyLink.href     = window.URL.createObjectURL( blob );
document.body.appendChild( dammyLink );
dammyLink.click();
document.body.removeChild( dammyLink );
```
のような形になります。（仮置きにたどり着くまで時間がかかりました）
これらをテンプレートの実装部分に追記します。
```javascript:Template.js
javascript:(
    function(){
        function sleep( sec ){
            return new Promise( resolve => setTimeout( resolve , sec * 1000 ) );
        }
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                    await sleep( 3 );
                }
                let badgeList = [];
                let badges    = card.querySelectorAll('lwc-tbui-badge');
                for( let i = 0 ; i < badges.length ; i++ ){
                    let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
                    let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
                    let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
                    let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
                    let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1].innerText
                    let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
                    badgeList.push([
                        '"' + getName + '"',
                        '"' + getYear + '-' + getMonth + '-' + getDay + '"',
                        '"' + getPoint.replace( ',' , '' ) + '"' ,
                        '"' + getImage + '"'
                    ]);
                }
                let data      = badgeList.map( ( badge ) => badge.join( ',' ) ).join( '\n' );
                let bom       = new Uint8Array( [ 0xEF , 0xBB , 0xBF ] );
                let blob      = new Blob( [ bom , data ] , { type : 'text/csv' } );
                let dammyLink = document.createElement( 'a' );
                dammyLink.download = 'TrailblazerMeBadges.csv';
                dammyLink.href     = window.URL.createObjectURL( blob );
                document.body.appendChild( dammyLink );
                dammyLink.click();
                document.body.removeChild( dammyLink );
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

## 完成形
```javascript:Template.js
javascript:(
    function(){
        function sleep( sec ){
            return new Promise( resolve => setTimeout( resolve , sec * 1000 ) );
        }
        async function main(){
            let footer  = document.querySelector('wes-footer').shadowRoot;
            let selects = footer.querySelector('lwc-wes-select');
            let select  = selects.shadowRoot.querySelector('select');
            const lang  = select.value
            if( lang == 'en' ){
                let card   = document.querySelector('tbme-profile-badges').shadowRoot.querySelector('lwc-tbui-card');
                let link   = card.querySelector('lwc-tbui-card-footer-link');
                let button = link.shadowRoot.querySelector('lwc-tds-button');
                while( true ){
                    if( button.querySelector('span').innerText == 'Show More' ){
                        button.click();
                    } else {
                        break;
                    }
                    await sleep( 3 );
                }
                let badgeList = [];
                let badges    = card.querySelectorAll('lwc-tbui-badge');
                for( let i = 0 ; i < badges.length ; i++ ){
                    let getName  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').alt;
                    let getYear  = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[2]
                    let getMonth = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[0]
                    let getDay   = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[0].innerText.split('/')[1]
                    let getPoint = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelectorAll('dd')[1].innerText
                    let getImage = badges[i].shadowRoot.querySelector('lwc-tbui-popover').querySelector('img').src;
                    badgeList.push([
                        '"' + getName + '"',
                        '"' + getYear + '-' + getMonth + '-' + getDay + '"',
                        '"' + getPoint.replace( ',' , '' ) + '"' ,
                        '"' + getImage + '"'
                    ]);
                }
                let data      = badgeList.map( ( badge ) => badge.join( ',' ) ).join( '\n' );
                let bom       = new Uint8Array( [ 0xEF , 0xBB , 0xBF ] );
                let blob      = new Blob( [ bom , data ] , { type : 'text/csv' } );
                let dammyLink = document.createElement( 'a' );
                dammyLink.download = 'TrailblazerMeBadges.csv';
                dammyLink.href     = window.URL.createObjectURL( blob );
                document.body.appendChild( dammyLink );
                dammyLink.click();
                document.body.removeChild( dammyLink );
            } else {
                alert( 'Please change the language setting to English' );
            }
        }
        main();
    }
)();
```

## 動かない問題が発生
コンソールで動作確認の後、ブックマークに設定して動作確認をすると、<font color="Red">**動かない！！なぜ！？**</font>

## 解決策
色々と調べてみた結果、とりあえずコードが長すぎるっぽいので[Closure Compiler Service](https://closure-compiler.appspot.com/home)でES2017へコンパイルしてみると動作しました。
```javascript
javascript:(
    function(){
        function d(a){
            return new Promise(b=>setTimeout(b,1E3*a))
        }
        (
            async function(){
                if("en"==document.querySelector("wes-footer").shadowRoot.querySelector("lwc-wes-select").shadowRoot.querySelector("select").value){
                    for(var a=document.querySelector("tbme-profile-badges").shadowRoot.querySelector("lwc-tbui-card"),b=a.querySelector("lwc-tbui-card-footer-link").shadowRoot.querySelector("lwc-tds-button");;){
                        if("Show More"==b.querySelector("span").innerText)b.click();
                        else break;
                        await d(3)
                    }
                    b=[];
                    a=a.querySelectorAll("lwc-tbui-badge");
                    for(let c=0;c<a.length;c++){
                        let e=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelector("img").alt,f=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[2],g=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[0],h=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[0].innerText.split("/")[1],k=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelectorAll("dd")[1].innerText,l=a[c].shadowRoot.querySelector("lwc-tbui-popover").querySelector("img").src;
                        b.push(['"'+e+'"','"'+f+"-"+g+"-"+h+'"','"'+k.replace(",","")+'"','"'+l+'"'])
                    }
                    b=b.map(c=>c.join(",")).join("\n");
                    a=new Uint8Array([239,187,191]);
                    b=new Blob([a,b],{type:"text/csv"});
                    a=document.createElement("a");
                    a.download="TrailblazerMeBadges.csv";
                    a.href=window.URL.createObjectURL(b);document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a)
                }else alert("Please change the language setting to English")
            }
        )()
    }
)();
```

## まとめ
Bookmarkletを作ったのは初めてでしたが、色々と使えるかもしれないので少し学習の意欲が湧きました。
また、これまでLWC関連でJavascriptを書くことはありましたが、標準的なJavascriptの知識の少なさが目に見えたので、今後も何かしらの学習をしていこうかと思いました。

## 更新情報
2023-08-04 Trailheadのプロファイルページの表示変更に伴う内容修正を行いました