検索条件
全5件
(1/1ページ)
const NAMEPREF = 1
を const NAMEPREF = 0
に書き換えてください。// Copyright 2021-2025 HASEBA Junya // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ==UserScript== // @name soul2naga // @namespace lions.blue // @icon http://1.gravatar.com/avatar/6fa3836d10d691125749472297cf516a // @version 1.0.0 // @description downloadlogsで取得する牌譜をNAGAで解析可能な形式に変換する // @include https://mahjongsoul.game.yo-star.com/* // @include https://game.mahjongsoul.com/* // @include https://majsoul.union-game.com/0/* // ==/UserScript== (function() { // ダウンロードリンクのhref属性において、牌譜データに先行する部分の文字列。 const DOWNLOAD_HREF_PREFIX = "data:text/plain;charset=utf-8,"; // 天鳳牌譜エディタのURLにおいて、牌譜データに先行する部分の文字列。 const EDITOR_URL_PREFIX = "https://tenhou.net/6/#json="; // ダウンロードイベントの前に割り込んで牌譜データを書き換える。 document.addEventListener("click", function(e) { const links = document.body.getElementsByTagName("a"); for (let i = 0; i < links.length; i++) { if (isDownloadLink(links[i])) { const soulJson = fetchSoulJson(links[i]); const urls = createViewerUrls(soulJson); links[i].href = buildDownloadHref(urls); links[i].download = buildFileName(links[i].download); return; } } }, {capture: true}); /** * オブジェクトをディープコピーする。 * * @param {Object} src コピー対象のオブジェクトを指定する。 * @returns {Object} 複製したオブジェクトを返す。 */ function deepCopy(src) { // JSON文字列化してからオブジェクトに戻すことでディープコピーを実現する。 return JSON.parse(JSON.stringify(src)); } /** * ダウンロードリンクであるか判定する。 * * @param {HTMLElement} element 判定対象の要素を指定する。 * @returns {Boolean} ダウンロードリンクの場合はtrue、それ以外の場合はfalseを返す。 */ function isDownloadLink(element) { // href属性の先頭部分で判定する。 return element.href.startsWith(DOWNLOAD_HREF_PREFIX); } /** * ダウンロードリンクから雀魂の牌譜データを抽出する。 * * @param {HTMLElement} element ダウンロードリンクを指定する。 * @returns {String} 雀魂の牌譜データを返す。 */ function fetchSoulJson(element) { // href属性から牌譜データを抽出する。 return decodeURIComponent(element.href.replace(DOWNLOAD_HREF_PREFIX, "")); } /** * ダウンロードリンクのhref属性を組み立てる。 * * @param {Array<String>} urls 牌譜エディタのURL群を指定する。 * @returns {String} ダウンロードリンクのhref属性を返す。 */ function buildDownloadHref(urls) { // ダウンロードリンクのhref属性を組み立てる。 return DOWNLOAD_HREF_PREFIX + encodeURIComponent(urls.join("\n")); } /** * ダウンロードファイル名を組み立てる。 * * @param {String} baseFileName もともとのダウンロードファイル名を指定する。 * @returns {String} 組み立てたダウンロードファイル名を返す。 */ function buildFileName(baseFileName) { // 卓名を雀魂っぽく変換し、拡張子を.txtに変更する。 return toSoulTable(baseFileName).replace(".json", ".txt"); } /** * 雀魂の牌譜JSONを牌譜エディタのURL群に変換する。 * * @param {String} soulJson 雀魂の牌譜JSONを指定する。 * @returns {Array<String>} 牌譜エディタのURL群を返す。 */ function createViewerUrls(soulJson) { // 雀魂の牌譜JSONをオブジェクトに変換する。 const soulPaifu = JSON.parse(soulJson); // title内の卓名を雀魂っぽく変換する。 // // 変換前のtitle: // "title": [ "玉の間南喰赤", "2021/10/20 20:48:01" ] // 変換後のtitle: // "title": [ "玉の間四人南", "2021/10/20 20:48:01" ] const title = deepCopy(soulPaifu.title); title[0] = toSoulTable(title[0]); const name = deepCopy(soulPaifu.name); const encodedName = name.map(function(v) { return encodeURIComponent(v); }); // rule内の卓名を雀魂っぽく変換する。 // // 変換前のrule: // "rule": { "disp": "玉の間南喰赤", "aka53": 1, "aka52": 1, "aka51": 1 } // 変換後のrule: // "rule": { "disp": "玉の間四人南", "aka53": 1, "aka52": 1, "aka51": 1 } const rule = deepCopy(soulPaifu.rule); rule.disp = toSoulTable(rule.disp); // logを局ごとのデータに分割し、牌譜エディタのURL群として返す。 return soulPaifu.log.map(function(v) { return EDITOR_URL_PREFIX + JSON.stringify({ "title": title, "name": encodedName, "rule": rule, "log": [toNagaLog(v)], }); }); } /** * 卓名を雀魂っぽく変換する。 * * @param {String} tenhouTable 天鳳っぽい卓名を指定する。 * @returns {String} 雀魂っぽい卓名を返す。 */ function toSoulTable(tenhouTable) { // 表記の好みの問題なので、必ずしも必要となる処理ではない。 return tenhouTable.replace("南喰赤", "四人南"); } /** * logをNAGAが解析可能な形式に変換する。 * * @param {Array<Array>} soulLog 雀魂形式のlogを指定する。 * @returns {Array<Array>} NAGAで解析可能な形式のlogを返す。 */ function toNagaLog(soulLog) { // 流局のデータは変換の必要がない。 if (soulLog[16].length < 3) { return soulLog; } const nagaLog = deepCopy(soulLog); // 当該局の場風を算出する。 // // 局を表す数字と意味: // 0 => 東1局, 1 => 東2局, ... const prevalent = ["東", "南", "西", "北"][Math.floor(nagaLog[0][0] / 4)]; // 役名をNAGAが解析可能な表記に変換する。 // ダブロン・トリロンに対応するため複数回繰り返す。 for (let i = 1; i < nagaLog[16].length; i += 2) { // 当該局における和了者の自風を設定する。 // // 算出方法: // (和了者のプレイヤー番号 - 親の位置 + 4) % 4 const seat = ["東", "南", "西", "北"][ (nagaLog[16][i].indexOf(Math.max(...nagaLog[16][i])) - (nagaLog[0][0] % 4) + 4) % 4 ]; // 役名をNAGAが解析可能な表記に変換する。 nagaLog[16][i + 1] = nagaLog[16][i + 1].slice(0, 4).concat( nagaLog[16][i + 1].slice(4).map(function(v) { return toNagaHand(v, prevalent, seat); } )); } // 変換後のlogを返す。 return nagaLog; } /** * 役名をNAGAが解析可能な表記に変換する。 * * @param {String} hand 和了役を指定する。 * @param {String} prevalent 場風を指定する。 * @param {String} seat 和了者の自風を指定する。 * @returns {String} NAGAで解析可能な表記の役名を返す。 */ function toNagaHand(hand, prevalent, seat) { // 対応が必要な役が判明次第、随時追加する。 switch(hand) { case "役牌:場風牌(1飜)": return `場風 ${prevalent}(1飜)`; case "役牌:自風牌(1飜)": return `自風 ${seat}(1飜)`; case "ダブル立直(2飜)": return "両立直(2飜)"; default: return hand; } } })();
S
キーを押してください。設定が正しく行われていれば、牌譜エディタのURLが並んだテキストファイルをダウンロードできるはずです。https://tenhou.net/6/#json=牌譜データ
という形式なので、この 牌譜データ
の部分に雀魂の牌譜を突っ込むことが最終目標になります。downloadlogs.js
の NAMEPREF
の値を 0
に設定しておきましょう。ダウンロードする牌譜データ内の役名が日本語になります。S
キーを押してください。これまでの手順に誤りがなければ、牌譜データのダウンロードが始まるはずです。// ダウンロードした牌譜データ { "title": [...], "name": [...], "rule": [...], "log": [ [東1局の牌譜], [東2局の牌譜], ... ], ... }NAGAでは局単位の牌譜データを読み込むため、以下のように局数分のJSONに分割してあげる必要があります。
// 東1局 { "title": [...], "name": [...], "rule": [...], "log": [ [東1局の牌譜] ] } // 東2局 { "title": [...], "name": [...], "rule": [...], "log": [ [東2局の牌譜] ] } ...
title
、 name
、 rule
、 log
以外の項目は削除しても問題ありません。https://tenhou.net/6/#json="title":[...],"name":[...],"rule":[...],"log":[[...]]
のようなURLを組み立てられるようになりました。これでNAGAに牌譜を解析させることができますね。お疲れ様でした!import json import sys def create_viewer_urls(soul_json): """ 雀魂の牌譜JSONを牌譜エディタURL群に変換する。 Args: soul_json (str): 雀魂の牌譜JSONを指定する。 Returns: list[str]: 牌譜エディタURL群を返す。 """ # 雀魂の牌譜JSONを辞書に変換する。 soul_paifu = json.loads(soul_json) # title内の卓名を雀魂っぽく変換する。 # # 変換前のtitle: # "title": [ "玉の間南喰赤", "2021/10/20 20:48:01" ] # 変換後のtitle: # "title": [ "玉の間四人南", "2021/10/20 20:48:01" ] title = soul_paifu['title'].copy() title[0] = to_soul_table(title[0]) # rule内の卓名を雀魂っぽく変換する。 # # 変換前のrule: # "rule": { "disp": "玉の間南喰赤", "aka53": 1, "aka52": 1, "aka51": 1 } # 変換後のrule: # "rule": { "disp": "玉の間四人南", "aka53": 1, "aka52": 1, "aka51": 1 } rule = soul_paifu['rule'].copy() rule['disp'] = to_soul_table(rule['disp']) # logを局ごとのデータに分割し、牌譜エディタURL群として返す。 return ['https://tenhou.net/6/#json=' + json.dumps({ 'title': title, 'name': soul_paifu['name'], 'rule': rule, 'log': [to_naga_log(log)], }, ensure_ascii=False, separators=(',', ':')) for log in soul_paifu['log']] def to_soul_table(tenhou_table): """ 卓名を雀魂っぽく変換する。 Args: tenhou_table (str): 天鳳っぽい卓名を指定する。 Returns: str: 雀魂っぽい卓名を返す。 """ # 表記の好みの問題なので、必ずしも必要となる処理ではない。 return tenhou_table.replace('南喰赤', '四人南') def to_naga_log(soul_log): """ 局データをNAGAが解析可能な形式に変換する。 Args: soul_log (list[list]): 雀魂形式のlogを指定する。 Returns: list[list]: NAGAで解析可能な形式のlogを返す。 """ # 流局時のデータは変換の必要がない。 if len(soul_log[16]) < 3: return soul_log naga_log = soul_log.copy() # 当該局の場風を設定する。 # # 局を表す数字と意味: # 0 => 東1局, 1 => 東2局, ... prevalent = ['東', '南', '西', '北'][naga_log[0][0] // 4] # 当該局の和了者の自風を設定する。 # # 算出方法: # (和了者のプレイヤー番号 - 親の位置) % 4 seat = ['東', '南', '西', '北'][ (max(enumerate(naga_log[16][1]), key=lambda x: x[1])[0] - (naga_log[0][0] % 4)) % 4 ] # 役名をNAGAが解析可能な表記に変換する。 naga_log[16][2][4:] = [to_naga_hand(hand, prevalent, seat) for hand in naga_log[16][2][4:]] # 変換後のlogを返す。 return naga_log def to_naga_hand(hand, prevalent, seat): """ 役名をNAGAが解析可能な表記に変換する。 Args: hand (str): 和了役を指定する。 prevalent (str): 場風を指定する。 seat (str): 和了者の自風を指定する。 Returns: str: NAGAで解析可能な表記の役名を返す。 """ # 対応が必要な役が判明次第、随時追加する。 if hand == '役牌:場風牌(1飜)': return f"場風 {prevalent}(1飜)" elif hand == '役牌:自風牌(1飜)': return f"自風 {seat}(1飜)" elif hand == 'ダブル立直(2飜)': return '両立直(2飜)' else: return hand if __name__ == '__main__': for url in create_viewer_urls(''.join(sys.stdin.readlines())): print(url)
※ここに牌譜データを貼り付けるにゃ!
の部分に牌譜データを埋め込んでください。if __name__ == '__main__': soul_json = """ ※ここに牌譜データを貼り付けるにゃ! """ for url in create_viewer_urls(soul_json): print(url)続いて、 Paiza.io にアクセスします。入力欄(背景色が黒い領域)に変更後のスクリプトを貼り付け、実行ボタンを押してください。スクリプトに誤りがなければ、画面下部のコンソールに牌譜エディタのURLが表示されるはずです。