League Of LegendsのオーバーレイをJavaScriptで作れたらいいよね!という話です。
LoLの世界大会であるWorldsやMSIのようなスコアボードやトップバーを作っていましたが、Python+OBSで作っていたため、シーンアイテム数が膨大になり、レイアウトの修正が難しくなっていました。
Pythonでゴリゴリで作っていたので「配布しても誰も修正できないじゃん・・・?」という漠然とした思いがありました。
「OBSってブラウザソースが表示できるじゃん?」と思い出し、JavaScript+HTMLでオーバーレイを作れれば、配布も改変も容易にできるのでは!という思い付きです。
JavaScriptでの利点
JavaScriptで作成する利点です。
- ブラウザで動くため、開発環境構築が不要
- レイアウトの修正もHTMLなら容易
- OBSの「ブラウザソース」で簡単に動く
配布が簡単・誰でも実行可能・誰でも修正可能というのがJavaScriptの利点です。
お試し
お試しとしてJavaScriptで分間CSを表示するJavaScriptを作ってみます。
JavaScriptのfetchでLiveClientDataAPIからCreepScoreを取得し、分間何CSとれたか表示するオーバーレイです。
お試しソース
結論から言うと、「LiveClientDataAPIがブラウザからの呼び出しを許容していない」ため、JavaScript単体での実現はできませんでした。
以下がお試しソースと発生したエラーです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>LoL 分間CSモニター</title>
<style>
body {
margin: 0;
padding: 0;
background: #0b0b10;
color: #f5f5f5;
font-family: system-ui, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.cs-container {
background: #15151f;
border: 1px solid #2f2f40;
border-radius: 12px;
padding: 24px 32px;
text-align: center;
min-width: 260px;
}
.cs-title {
margin: 0 0 12px;
font-size: 20px;
color: #c8c8ff;
}
.cs-value {
font-size: 56px;
font-weight: 700;
color: #ffdf6b;
margin-bottom: 12px;
}
.cs-sub {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #b0b0c8;
margin-bottom: 10px;
gap: 16px;
}
.cs-status {
font-size: 12px;
color: #8888aa;
}
</style>
</head>
<body>
<div class="cs-container">
<h1 class="cs-title">分間CS</h1>
<div class="cs-value" id="csPerMinute">--.-</div>
<div class="cs-sub">
<span>現在CS: <span id="currentCs">-</span></span>
<span>経過時間: <span id="gameTime">-</span></span>
</div>
<div class="cs-status" id="statusMessage">Live Client API 待機中...</div>
</div>
<script>
const PROXY_URL = "https://127.0.0.1:2999/liveclientdata/allgamedata";
const csPerMinuteEl = document.getElementById("csPerMinute");
const currentCsEl = document.getElementById("currentCs");
const gameTimeEl = document.getElementById("gameTime");
const statusMessageEl = document.getElementById("statusMessage");
async function fetchJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
function formatTime(seconds) {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
}
async function updateCsPerMinute() {
try {
statusMessageEl.textContent = "更新中...";
// Flask のレスポンスは { ok: true, data: {...} }
const api = await fetchJson(PROXY_URL);
if (!api.ok) {
statusMessageEl.textContent = "Live Client API に接続できません";
return;
}
const data = api.data; // ← 本物の Live Client API の JSON
// Riot ID でプレイヤー特定
const activeRiotId = data.activePlayer.riotId;
const me = data.allPlayers.find(p => p.riotId === activeRiotId);
if (!me) {
statusMessageEl.textContent = "プレイヤー情報が見つかりません";
return;
}
const cs = me.scores.creepScore;
const gameTime = data.gameData.gameTime;
const minutes = gameTime / 60;
const csPerMinute = minutes > 0 ? cs / minutes : 0;
currentCsEl.textContent = cs;
gameTimeEl.textContent = formatTime(gameTime);
csPerMinuteEl.textContent = csPerMinute.toFixed(1);
statusMessageEl.textContent = "更新完了";
} catch (err) {
console.error(err);
statusMessageEl.textContent = "Live Client API に接続できません";
csPerMinuteEl.textContent = "--.-";
}
}
updateCsPerMinute();
setInterval(updateCsPerMinute, 10000);
</script>
</body>
</html>
Access to fetch at ‘https://127.0.0.1:2999/liveclientdata/allgamedata’ from origin ‘null’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header has a value ‘https://127.0.0.1:2999’ that is not equal to the supplied origin. Have the server send the header with a valid value.
ブラウザにはCORSというCross-Origin Resource Sharing(クロスオリジン・リソース・シェアリング)という仕組みがあります。CORSは異なるオリジン(≒アドレス)へのアクセスを阻止する仕組みです。
LiveClientDataAPIはJavaScriptからのアクセスを想定していないため、CORSヘッダーを返さず、CORSエラーが発生します。
CORSエラー対策
「LiveClientDataAPI は CORS ヘッダーを返さないため、ブラウザから直接アクセスすると CORS エラーが発生します。
この問題を解決するために、「LiveClientDataAPI のデータを取得し、必要な CORS ヘッダーを付与して返す “CORS 対応 API プロキシ”」 を 作成します。
プロキシを作る、というとハードルが高く感じますが、とても簡単な作りとなっています。
Copilotには「Node.jsがオススメ」と言われましたが、まずは形にするため慣れているPythonで作成します。
やっていることはとても簡単です。
①LiveClientDataAPIにアクセス
②CORSヘッダーを付与する
の2点です。
以下「CORS 対応 API プロキシ」を実行することでJavaScriptからLiveClientDataAPIにアクセスすることができるようになります。
from flask import Flask, jsonify
import requests
import urllib3
# 証明書の警告を無効化
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
app = Flask(__name__)
session = requests.Session()
# LoLクライアントのローカルAPIエンドポイント
BASE = "https://127.0.0.1:2999/liveclientdata"
# LoLクライアントからデータを取得する関数
def fetch_lcu(path):
try:
r = session.get(f"{BASE}/{path}", verify=False, timeout=0.3)
return {"ok": True, "data": r.json()}
except Exception as e:
return {"ok": False, "error": str(e)}
# CORS対策のため、レスポンスにヘッダーを追加
@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = "*"
return response
# LoLクライアントから全ゲームデータを取得するエンドポイント
@app.route("/allgamedata")
def allgamedata():
return jsonify(fetch_lcu("allgamedata"))
if __name__ == "__main__":
app.run(port=3000, debug=True)
完成版
お試しソースのURLを変更します。
JavaScript側のURLを以下のように「CORS 対応 API プロキシ」に変更します。
const PROXY_URL = "https://127.0.0.1:2999/liveclientdata/allgamedata";
const PROXY_URL = "http://localhost:3000/allgamedata";
完成版ソース全量
重複しますが見やすくするために分間CS表示JavaScriptとCORS対応APIプロキシのソース全量をまとめます。
分間CS表示JavaScript
分間CS表示JavaScriptのソース全量です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>LoL 分間CSモニター</title>
<style>
body {
margin: 0;
padding: 0;
background: #0b0b10;
color: #f5f5f5;
font-family: system-ui, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.cs-container {
background: #15151f;
border: 1px solid #2f2f40;
border-radius: 12px;
padding: 24px 32px;
text-align: center;
min-width: 260px;
}
.cs-title {
margin: 0 0 12px;
font-size: 20px;
color: #c8c8ff;
}
.cs-value {
font-size: 56px;
font-weight: 700;
color: #ffdf6b;
margin-bottom: 12px;
}
.cs-sub {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #b0b0c8;
margin-bottom: 10px;
gap: 16px;
}
.cs-status {
font-size: 12px;
color: #8888aa;
}
</style>
</head>
<body>
<div class="cs-container">
<h1 class="cs-title">分間CS</h1>
<div class="cs-value" id="csPerMinute">--.-</div>
<div class="cs-sub">
<span>現在CS: <span id="currentCs">-</span></span>
<span>経過時間: <span id="gameTime">-</span></span>
</div>
<div class="cs-status" id="statusMessage">Live Client API 待機中...</div>
</div>
<script>
const PROXY_URL = "http://localhost:3000/allgamedata";
const csPerMinuteEl = document.getElementById("csPerMinute");
const currentCsEl = document.getElementById("currentCs");
const gameTimeEl = document.getElementById("gameTime");
const statusMessageEl = document.getElementById("statusMessage");
async function fetchJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
function formatTime(seconds) {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
}
async function updateCsPerMinute() {
try {
statusMessageEl.textContent = "更新中...";
// Flask のレスポンスは { ok: true, data: {...} }
const api = await fetchJson(PROXY_URL);
if (!api.ok) {
statusMessageEl.textContent = "Live Client API に接続できません";
return;
}
const data = api.data; // ← 本物の Live Client API の JSON
// Riot ID でプレイヤー特定
const activeRiotId = data.activePlayer.riotId;
const me = data.allPlayers.find(p => p.riotId === activeRiotId);
if (!me) {
statusMessageEl.textContent = "プレイヤー情報が見つかりません";
return;
}
const cs = me.scores.creepScore;
const gameTime = data.gameData.gameTime;
const minutes = gameTime / 60;
const csPerMinute = minutes > 0 ? cs / minutes : 0;
currentCsEl.textContent = cs;
gameTimeEl.textContent = formatTime(gameTime);
csPerMinuteEl.textContent = csPerMinute.toFixed(1);
statusMessageEl.textContent = "更新完了";
} catch (err) {
console.error(err);
statusMessageEl.textContent = "Live Client API に接続できません";
csPerMinuteEl.textContent = "--.-";
}
}
updateCsPerMinute();
setInterval(updateCsPerMinute, 10000);
</script>
</body>
</html>
CORS対応APIプロキシ
CORS対応APIプロキシのソース全量です。
from flask import Flask, jsonify
import requests
import urllib3
# 証明書の警告を無効化
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
app = Flask(__name__)
session = requests.Session()
# LoLクライアントのローカルAPIエンドポイント
BASE = "https://127.0.0.1:2999/liveclientdata"
# LoLクライアントからデータを取得する関数
def fetch_lcu(path):
try:
r = session.get(f"{BASE}/{path}", verify=False, timeout=0.3)
return {"ok": True, "data": r.json()}
except Exception as e:
return {"ok": False, "error": str(e)}
# CORS対策のため、レスポンスにヘッダーを追加
@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = "*"
return response
# LoLクライアントから全ゲームデータを取得するエンドポイント
@app.route("/allgamedata")
def allgamedata():
return jsonify(fetch_lcu("allgamedata"))
if __name__ == "__main__":
app.run(port=3000, debug=True)
実行イメージ
分間CSJavaScriptの実行イメージは以下の通りです。

作成したJavaScriptをOBSのブラウザソースで取り込むことで配信画面に分間CSを表示することができます。オーバーレイのデザインはCSSで変更することができます。
今後
JavaScriptによるLeague Of Legendsのオーバーレイ作成について目途がつきました。
今後はスコアボードやトップバーの作成、Node.jsによる「CORS対応プロキシ安定板」の作成を進めていいこうと思います。


コメント