19日に更新してた

アフィリエイトはないよ

r0706あたりの雑記

  • PDICunicode 版で学辞郎の SIL、SVL を csv に抽出してみたら、抽出ファイルが python utf8 で読み込めなくて、エディタでファイル開いて確認してみたら utf16 なのね。

pdic.la.coocan.jp

  • 学辞朗の抽出をレベルごとにするのが面倒な時は書き出した CSV 内に level が入っているから後からなんとかするも良しな感じ。
  • 正規表現は ChatGPT に任せるのが楽で早い感じなので、SIL、SVL の例文抽出は例文突っ込んで任せたほうがいいと思っていたけれど、どうしても引っかかる文字があったりして、結局自分で細かい修正入れないといけなかった。
  • 学辞郎の例文抽出は SVL が19816、SIL が6000、計25816となった。あとは勉強するだけ…、いつもながら作るのが楽しいだけなんだよな。
  • puppeteer-stealth もどきを chromedp でやってみようかと思ったら、ノートン君が検疫してくれた… 仕方がないので書き換えて対応。下記サイトでなんとか全部対応。

bot.sannysoft.com

  • headless にするとユーザーエージェントが変わるのか、知らなかった。
  • ffmpeg のエラーログを ChatGPT に突っ込んだら、そういえばそうだった系の答えが帰ってきて自分の記憶力にがっかりする。
  • Headed Chrome でうまく動いていても Headless にすると User-Agent を Headed に合わせてもうまく動かない原因究明は面倒で放置、pupeteer-stealth もどき入れても駄目だった…とか言ってましたが、windowsize を指定したらサクサク動くようになりました。
  • puppeteer-sthealth もどきの chromedp のオプション。disable-gpu をいれると少し遅くなる。
opts := append(chromedp.DefaultExecAllocatorOptions[:],
	chromedp.NoFirstRun,
	chromedp.NoDefaultBrowserCheck,
	chromedp.Flag("disable-extensions", false),
	chromedp.Flag("enable-automation", false),

	chromedp.Flag("headless", true),
	chromedp.WindowSize(1280, 800),
	chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"),
)
  • ノートンの検疫に掛かってイライラしていたけれど、build して実行すると 2 回目からはかからないことに今更気がついた。
  • PC のブラウザから、ChatGPT、Claude 4.0 Sonnet、Gemini 2.5 Pro に golangソースコード与えて最適化してくださいと頼んだら、取りあえず動くように出てきたのが Gemini だけだった。しかもしっかり実行速度がはやくなっていた。golang だから?やっぱ Google
  • うなぎのチェーンが流行っているけど、うなぎって絶滅危惧種から外れてないよね。
  • アクアリウムでうなぎを販売して安価な繁殖方法を愛好家の知識でと思ったがうなぎ養殖は許可がいるから難しいかも。

www.jfa.maff.go.jp

  • 自作キーボードが流行っているけれど、スマホでわけわからん広告をクリックせずに済むようなキーボードないかな。フルのキーボードはいらないけれど矢印とかエンターとかタブとかだけのが。
  • go build や go run 時のノートンの検疫、除外ディレクトリを C:\Users\myid\AppData\Local\Temp\go-build* にしたらなんとかなった。...\Temp\* じゃだめなのね。
  • ChatGPT 、Gemini 、Claude の話ばっかりで NPU うまく使えてる話があまり流れてこなくてええのかな、インテルとか AMD とか。マイクロソフトも OpenAI と揉めてるみたいだし。
  • Gemini 、sync.WaitGroup が苦手なのか?wg.Add(1) 、wg.Done() の位置がよろしくない。それだけ直したらサクサク動く。
  • アオハタのアップルシナモンジャム、買いに行ったらなかったので調べてみたら生産停止…
  • codoc の登録をしてみました。よろしくお願いします

【golang】chromedp を使って radiko の再生に必要なパラメータを取得する(技術検証)

chatGPT に描いてもらった【golang】chromedp を使って radiko の再生に必要なパラメータを取得する(技術検証)の絵

注意事項

本記事は、技術的な学習・検証を目的としてchromedp を使って radiko の再生に必要なネットワークパラメータ(主に認証トークンなど)を取得する方法を紹介するものです。

ただし、以下の点に十分ご注意ください。

  • 本記事で紹介する手法は radiko の公式仕様に基づいた利用方法ではありません。
  • radiko の番組データは著作権で保護されており、無断で録音・保存・再配布することは禁止されています。
  • radiko利用規約に反する可能性のある行為(ダウンロード、自動化、外部配信など)を行う際は、必ず自己責任でお願いします。
  • 本記事の内容を利用したことによって生じた いかなる損害・トラブルについても筆者および当ブログは一切の責任を負いません。

背景

radiko ダウンロード などで調べても、実際にキャプチャする手法をコード付きで解説している情報はあまり見かけませんでした。
そこで、chromedp を用いてネットワークイベントを監視し、認証情報などを抜き出すサンプルを作成しました。

radiko プレミアムなどの契約は行っておらず、ブラウザでアクセス可能なタイムフリー再生ページでの検証を行っています。

前提・動作概要

  • 使用言語: Go(chromedp 使用)
  • 動作対象: タイムフリー再生URL
  • 使用環境: GUI環境あり(※headless モードでは正常動作しません)
注意点
  • headless モードでは、全く進展しませんでした。現時点では回避策が見つかっていません。
  • ListenTarget を Run() より前に定義すると、動作が不安定になる場合があります。
  • コンテクストの cancel() は defer を使わず、イベント処理内で明示的に呼び出しています。

実行手順

タイムフリーで再生可能な URL を引数に渡して実行します。

コード

package main

import (
	"context"
	"fmt"
	"log"
	"net/url"
	"os"
	"strings"

	"github.com/chromedp/cdproto/network"
	"github.com/chromedp/chromedp"
)

func main() {
	URL := os.Args[1]

	// Chromeブラウザの実行コンテキストを生成(headless は無効)
	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), []chromedp.ExecAllocatorOption{
		chromedp.NoFirstRun,
		chromedp.NoDefaultBrowserCheck,
	}...)
	defer cancel()

	// メインの操作コンテキスト
	ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))

	// ブラウザ起動 & ページ操作
	if err := chromedp.Run(ctx,
		chromedp.Navigate("https://radiko.jp/"),

		chromedp.Click(`button.js-policy-accept`, chromedp.ByQuery),

		chromedp.Navigate(URL),

		chromedp.Click(`a.btn--play`, chromedp.ByQuery),

		chromedp.Click(`input[name="gender"][id="other"]`, chromedp.ByQuery),
                // 2009年11月10日にgolangが公開されています
		chromedp.SetValue(`select[name="birthYear"]`, "2009", chromedp.ByQuery),
		chromedp.SetValue(`select[name="birthMonth"]`, "11", chromedp.ByQuery),
		chromedp.Click(`button.questionare-form_submit`, chromedp.ByQuery),

		chromedp.Click(`//a[contains(text(), "再度表示しない")]`, chromedp.BySearch),

		chromedp.Click(`a.btn--primary-red`, chromedp.ByQuery),
	); err != nil {
		log.Fatal("Failed to enter radiko:", err)
	}

	ch := make(chan *network.EventRequestWillBeSent, 10)

	chromedp.ListenTarget(ctx, func(ev any) {
		if e, ok := ev.(*network.EventRequestWillBeSent); ok {
			if e.Request.Method == "GET" {
				ch <- e
			}
		}
	})

	for ev := range ch {
		for k, v := range ev.Request.Headers {
			if strings.Contains(k, "X-Radiko-AuthToken") {
				fmt.Println(k, v)

				close(ch)
				cancel()

				parsedURL, err := url.Parse(ev.Request.URL)
				if err != nil {
					panic(err)
				}
				for key, values := range parsedURL.Query() {
					for _, value := range values {
						fmt.Printf("%s = %s\n", key, value)
					}
				}
			}
		}
	}
}

補足

X-Radiko-AuthToken などの認証系ヘッダーは、再生開始後に発生する特定の GET リクエストに含まれます。

このようなトークンをキャプチャすることで、後続処理*1に活用できる可能性があります。

まとめ

chromedp を使うことで、ブラウザの動作をスクリプトから自動制御しつつ、ネットワークトラフィックをキャプチャできます。

ただし、radiko 側の仕様変更や規約変更により動作しなくなる可能性もあるため、常に最新の挙動を確認するようにしてください。

本手法はあくまで技術的興味・検証を目的としており、実運用は自己責任でお願いします。

この記事は人間が書いたものを AI にリライトしてもらっています。*2

*1:※合法的な範囲で

*2:書いたものを chatGPT に入力して書き直してもらったらこんな感じになりました。コードは自分で書いたものですがコメントを挿入されており、解説はほぼ全面的にリライトされています。

r0705あたりの雑記

  • Chrome のパスワードを自動作成してもらうとサイトによっては文字数が多かったり、英数字以外不可なのに入っていたりするので、なんとかしてもらえると嬉しい。
  • Gemini, Claude, DeepSeek, ChatGPT のそれぞれに"FXの過去足データを探しています。usdjpyの一分足データを何年か分ダウンロードできるサイトを教えてください。できればそのデータの時間の標準時も教えてください" と入れたら、課金の有無は指定しなかったのですがそれでもそれぞれ違うサイトを教えてくれて面白い。
  • メガネで色付きレンズを前につけられるタイプのフレームはあるけれど、花粉症対応のフレームはないね。
  • 本屋でツェッテルカステンの本を見かけて、スマホアプリでメモ管理するのもいいかもなと思って、google keep 入れてみたら便利。ツェッテルカステンしてないけど。
  • なんか NPU 積んだパソコンの話題が一気に LLM のバージョンアップからのバイブコーディング?になってて、次どこ行くんだろう。
  • 大阪ガスが冷食なら、他のガス会社はデータセンターで多角経営できそう。
  • 携帯が衛星電話化することで、「携帯も通じない辺境」ネタが使えなくなるような。どんな新しいのが出るのだろう。
  • 皮膚科で金属アレルギーって診断受けた人が「歯を抜いたら保険で歯を入れられないんだよ、入れ歯もブリッジも金属使うから」って言ってた、酷い話。
  • ソースネクストのスピークバディ1年版、楽天だとセールで12800円になってるけど、自社サイトだと変わらず。おまけにスピークバディのサイトだと最大20%引きっていうよくわからないことになってる4月末の土曜日。
  • chromedp を何故使うかというと、ノートン君が一番うるさくないから*1。割と何でもできるから悪くないが、Headless の成功率は低い*2。とりあえず、ウインドウ出しとけば大体なんとかなる、cloudflare 以外は。
  • windows 環境で chromedp のプログラムを仮想デスクトップの今使っているのと異なるデスクトップでウインドウを開くことが出来ないか調べてみているが、うまく調べられない。うまくいきそうでも、固まる。メモ帳だと簡単なんだが。chromedp から chrome タスクマネージャのデータは引けないみたいだし。
  • カランメソッドについて調べていたら、英語のハノンとの比較が出ていてどうも似ているらしい。それなら、英語のハノン的な練習問題をスマホで LLM つかって音声入出力でカランメソッドっぽくやるのはそんなに難しくなさそうだけれど、データ作るのが面倒。
  • 自分が知りたい内容の記事を書いてくれと ChatGPT に頼むと非常に面白いものが出てくるので、楽しい。
  • スマホアプリで使う ChatGPT のボイスモードは Gemini のよりいいけどウザい。Gemini はそもそも音声回答が出ないことがある、テキストは出ているのに。
  • LLM も個性があるようで、質問内容を打てば響くように意を汲み取ってくれるのと、何言ってるのって返事なのとある。いずれ課金しようと思ってはいるけれど、質問によってどれが汲んでくれるくれないが変わるからなかなか難しい。
  • 町内会の会費とか住民税の均等割の超過課税の有無や金額とか、家建てたり引っ越しする前にこそ知りたいことを比較するサイトってのはなかなか作りづらいよな。
  • 車の運転中にスマホいじっちゃいけないから、音声読み上げとか音声入力対応とかしてくれると便利なのにね。
  • Windows も npu 付いてるやつなら Copilot から 音声ファイルの文字起こし対応とかして欲しいけど、12でそうなるかな。なったら付いてるの検討する。
  • 個人的に Qwen で盛り上がってるな。個人的に Deepseek の回答は好きなので Qwen にも期待していたりする。
  • 作ろうかなと思っていた 2 択のページを Qwen で作ってもらった。体感1秒だもの、すごい時代だ。

0kkyit6d3dk10ljbipk6rg.on.drv.tw

  • 普段電車に乗るような生活をしてないのですが、久しぶりに乗ったらみんなスマホ見ながらワイヤレスイヤホンでなにか聞いている。若い方だけじゃなくてスーツ着たいい歳のお勤めしてる感じの方もそんな感じだったので、「しっかり時代に乗り遅れているな、自分」と感心した。
  • yt-dlp のソース解説をお願いしたら、Qwen は使い方を教えてくれた。ChatGPT、Gemini はきちんと解説してくれて、ChatGPT に至っては質問してたらダウンローダーの雛形*3を作り始める。
  • メルカリの出品者送料負担時の総額からの利益計算と利益からの総額計算を電卓叩くのが面倒になったので、JavaScript で作ってみた。基本自分用ですが、よろしかったらどうぞ。

0kkyit6d3dk10ljbipk6rg.on.drv.tw
0kkyit6d3dk10ljbipk6rg.on.drv.tw

  • 天王州アイルって東京だったんだ、大阪だと思ってた。それくらいの地方民が書いている
  • ドバイでヤギとっていう女の人の話が中東に留学経験のある人との話題に出たら、「多分見た目が欧米系の人なんだと思うよ、欧米系の女性は歩いているだけで求婚される位人気あるから貶めるのも同じでしょうし。アジア系好きはオタクかマニア」とのことでした。
  • 正規表現の記述を ChatGPT にお願いすると見事に良い感じのが出てくる、とりあえずに使うには便利。Gemini だとうまくいかない事が多い。
  • アンドロイドの電卓、計算結果をフリックすると下の桁の計算結果が表示されることに今気がついた。

play.google.com

  • かつては高校に真面目に通って字が綺麗で簿記3級を持っている人の就職は困らなかったけれど、ワードとエクセルのお陰でそのあたりの需要は持っていかれたから、今度は AI だよね。どこまで持っていかれるのやらと思う。

*1:rod 使おうとしたら検疫されて使わしてくれない

*2:やりたいようなサイトは大体うまくいかないが、目視確認できるからいいとしよう

*3:そのままでは動かない

r0704あたりの雑記

  • 喉が痛いので人から勧められた 龍角散ののどすっきり飴 を舐めたら痛みが引いた。すごくよく効くと感心。

www.ryukakusan.co.jp

  • 景品の皿目当てに超熟。抽選だから当たらないだろうなぁとは思う。

https://www.pasconet.co.jp/campaign/5/index.htmlwww.pasconet.co.jp

  • Anthropic Claude 3.7 Sonnet 無料でコード書かせてみたけれど、とんでもないな。自分で作るなら数週から数ヶ月かかるのが下手すりゃ数時間でできる。思いついたものが自分のできる言語でできない、仕方がないので後回しにしてた、が簡単にできるから契約しようか迷う。
  • 都会の人の「田舎で暮らす」というのをよく見かけるけれど、東京の人から見た田舎に入る人口2~30万人都市の駅近アパートとかマンションなら人間関係に悩むほどのことはないと思うけれど、そういうのじゃないのだろう。
  • Deepseek と Claude の無料プランを使ってちょっとしたものを作ってみたが、サクサク出来て面白い。
  • 気がついたら無料で Sonnet が使えず、Haiku になってる。
  • 英語のハノンの初級のダウンロードファイルをアップロード*1して slow , natural , その他に分けてダウンロードする web アプリ?を作ってみた。html に javascript ベタ書きでライブラリは CDN 由来です。

0kkyit6d3dk10ljbipk6rg.on.drv.tw

  • 瞬間英作文とか暗唱に近いし、受験勉強も内容がすぐ出てくるようにするのが肝心要と考えると、知識を反射的に回答できるように知識を準備するのがいわゆる勉強であると気がつくのに時間が掛かった。
  • QB ハウス、翌月末までの割引券をくれるようになったのはいいけれど、忙しいと渡し忘れてるしこっちももらうのを忘れてる。
  • 職場のパソコンが調子悪いとのことで購入権限者から相談あり、lenovo A100 が一番候補とのことも、「それ Win11Home だから駄目ですよ、その用途なら Pro じゃないと」と答える。一体型の N シリーズはある程度の需要あるだろうと思う。
  • 東京で桜が満開と聞いてすでに満開なのはどこだろうと調べてみたら、高知と福岡と出てきた。鹿児島でさえもうちょっと遅いらしい。
  • Pixel で Debian って話、内輪のイベントのマイクラサーバーとかいろいろと用途を思いつく、動くかどうか知らんが。現時点で用途はないけれど次のスマホはまた Android に決まった。
  • いろいろな LLM に、インターネット上で需要は多いけれど供給が少ない話題を教えて下さいと聞いて、面白そうなのを Gemini の deep research に突っ込んだら割と面白かったけれど、なんかもの足りない感じはある。
  • こういうレーザーエングレービングしてある腕時計、高級ブランドで出てきたのは意外だった。ゲームのコピーカセットと一緒の通販ページに載っているようなニューソウルとか新北京とかに通じるような感性のブランドがやるもんだとばっかり。

www.mauricelacroix.com

  • LLM 使ったプログラミング環境を整えるのにどういうのがいい?いろいろとスマホから無料枠の LLM に聞いて回るも、コストとか使用内容を考えて回答してくるのが Claude と Deepseek で Deepseek は自分ところのを勧めないという「いいの?、商売っ気ないなぁ」って感じ。
  • ふと思いついて英文法の本の例文の載っている1ページをスマホで撮影後 PDF にして Deepseek にあげて「このファイルは英語の教科書の一部です。覚えるべき例文が四角に囲まれて箇条書きになっています。行頭文字を取り除いて例文と訳を一行ニ列にしてcsvフォーマットにしてください。」としたら指示通りにしてくれたので、本から作る英単語リストは自炊したものを読み込ませて作るのが楽でいいのかも。
  • SMAP はメンバーでも広末涼子は容疑者なんだな。フジテレビは広末やるより中居やらないと。
  • Amazon プライムビデオに広告って話題になっているけれど、全部の値上げでないということはプライムビデオが不採算部門なのか、一番取りやすいところがプライムビデオなのか。
  • bing 壁紙のバージョンアップで挙動が変わったので設定を変えたらだいたい望むような感じになった。マイクロソフトええやん。
  • 4月に入ってちょっとしたらスパムが減った。バンバン来てたのが一日数通になった。
  • 某テストを受けようと CBT の空きを見たら都合がいい日が空いてなくて、ちょっと遠出することになった。
  • USB FAX モデムは音声対応できないのだろうか、できれば個人、中小企業向けの自動応答を LLM 使って簡単に構築できそう。FAX はプリンタ繋いどきゃいいし。まぁ、スマホで対応がメインになってきているから、難しいかもな。
  • なんとなく「英語やらないのもなぁ」と思って瞬間英作文をやっているのですが、自分の思った英文や疑問を Copilot にいれると説明してくれるので、知りたいことが知りたいときに解説されるのでとても楽しい。何回同じことを頼んでも嫌な顔をされないし、意味も類義語も例文も綴りの似た単語も瞬時に出してくれて、コードブロックに出してもらう*2と AnkiDroid に簡単にコピペできるのでいい感じ。
  • とりあえず無料版を使っている LLM ですが、なにか課金しようかなと思うも一週単位で状況が変わるのでなかなか決めかねている。
  • 万博行きたいけど夏だと暑そうだし、あまり暑くないうちは日程的に厳しいし。
  • 以前、Javascript で作ったコイン3枚法の易占*3を公開してみた。

0kkyit6d3dk10ljbipk6rg.on.drv.tw

*1:というか一枚HTMLなのですが

*2:太字なしと指定すると * を抜いてくれるので見やすい

*3:少しいじったかどうか覚えていない

r0703あたりの雑記

  • バレンタインのときにX(元ツイッター)で流行ったプレミアムガーナ 生チョコレート<アンフィニマン ヴァニーユ> がまだ売っていたので買ってみたが、クランキーチョコのほうが好み。貧乏舌だから仕方がない。
  • 仕事中の営業の電話とかうざいので、営業の電話から金取れるサービスが欲しい。
  • ヤマザキ春のパンまつりのシール台紙をスーパーで見かけて、スマホアプリで openCV 使って台紙に張ってあるシールの点数計算してくれるアプリとか作ろうかなと思ったが、探してみたらあったので作らなくていいかとなった。

www.yamazakipan.co.jp
play.google.com

  • panex のソルバー、いい方法が思いつかないのでとりあえず rust で作り直してみようかと、rust の本を再度読み始めた。
  • pdf ファイル用に A4 サイズのタブレットをちょっと使ってみたいと調べたら、13インチのタブレットがそんな感じらしい。新品だと ipad pro も air も2桁万円。teclast の T65 Max は 3.6万円。試しの出費には辛いな。
  • 郵便局はミニレターをプリンタで印刷する用に、A4サイズにして切り取りのミシン目をいれてくれると嬉しい。インクジェット官製はがきみたいに宛名ソフトで宛名も通信面も印刷できるようになればもっと使いたいと思う。
  • GPS腕時計ええなと思って、SEIKO アストロンの sbxd009 がシンプルでいいなと思うも、"製品スペックを見る"ページがないのでディスコンらしい。CITIZEN の CC4105-69E あたりデザインはいいのですが文字盤がうるさい。

www.seikowatches.com
citizen.jp

  • panex のソルバー、rust で golang のそのまま組んでみたら何かが上手く行っていないらしく golang より格段遅いものができた。しゃーない。
  • たしか借金の自殺は300万くらいが多かった記憶がある。統合失調症と女性への通り魔、司法の軽視などどんな判決が出るか注目の裁判になりそう。
  • panex のソルバー、久しぶりに試してみたらなんか遅くなっているのは何故?ソースコード同じはずなのに。

r0702あたりの雑記

  • キラーナンプレのソルバー作ろうかと思ったけれど、問題の入力が面倒そうなのでやめた。
  • 最近トマトジュースが美味しくて、イオンのペットボトルのをよく買っている。体調いい感じ。
  • 最近ノートンの仕様が変わって、自作アプリを一時的にブロックしてくれるようになった。
  • 自民党、比例の大臣、いつからよくなったの?
  • 海外でツリ目とかやってくる人間に「ヒットラー我が闘争で日本人ディスってたから、君もナチスなんだね」と言ったらどうなる?
  • AI で個の知性が問われなくなるのでは?なんて話もありますが、AI にする質問が適切かつ数が少ないほうが効率的だから、ボトルネックとしての人間が発生するだけのような気がする。
  • ノートン君、Chrome の LINE Share にブロックとか表示出すものの送信されていて、役に立っているのかどうかわからない状態。

chromewebstore.google.com

  • 何かと財務省は叩かれるけれど、負担が大きいのは社会保険料だから管轄が違う気がする。
  • クルトガメタルを店頭で売っているのをはじめて見た。
  • オレンズネロの調子が悪かったのでサイトから当てはまりそうな項目を調べて部品を注文して交換したら、調子良くなった。もうだめだから新品買うかなと思っていたところに部品が300円で買えたので、助かった。

www.pentel.co.jp

  • ノンアルのジョッキ缶をはじめて見たので買って帰って開けたら、とんでもなく泡が出てきて1/5くらいこぼれたので次から注意する。
  • マイナンバー保険証の話で、生活保護は全国一緒だけれど、子どもの医療費補助とか難病補助とかは市町村単位で異なるので対応に時間が掛かるという話で、あの辺りの形式を規格化して欲しいと後輩が言っていた。大手はともかく小さいところは大変らしい。
  • ちょっと気になっていた ziglang を調べてみた。僕が書くようなちょっとしたものなら rust より書きやすいかもと思うも、array とか slice とかの扱いがいまいちよくわからないので様子見で終わりそうな感じ。
  • iherb 800円くらいのもの単品で送料込み、千二三百円をカード払いで注文をしたら、梱包のクッションがなかった。破損などはなかったがビックリした。
  • 本屋さんブックチャームのガチャガチャをやってみたら丸善が出た。

gashapon.jp

【golang】8puzzle のソルバーを再帰で作ってみた

数独のソルバーを深さ優先で再帰で作ったからそのノリで作ってみました。3x3、4x4、5x5 と一応対応しております。

シャッフルして問題も供給する形にしました。

package main

import (
	"fmt"
	"math"
	"math/rand"
	"slices"
	"strings"
	"time"
)

const goal = "123456780"

// const goal = "123456789abcdef0"

// const goal = "123456789abcdefghijklmno0"

func main() {
	// fmt.Println(len(goal))

	// パズルをシャッフルする回数
	var count = 7
	q := repeatShuffle(count)
	size := sqrtInt(len(goal))
	fmt.Println("shuffle:", count)

	start := time.Now()

	// パズルを深さ優先で検索するときの深さの初期設定
	count = 10
	fmt.Println("count:", count)
l:
	checkSlice := []string{q}
	checkSlice, ok := calc(checkSlice, 1, count)
	if !ok {
		count += 10
		fmt.Println("count:", count)
		goto l
	} else {
		t := time.Since(start)
		fmt.Println("q.")
		for i := range size {
			fmt.Println("q", q[i*size:(i+1)*size])
		}
		fmt.Println("\nans.")
		for i, c := range checkSlice {
			for j := range size {
				fmt.Println(i, c[j*size:(j+1)*size])
			}
			fmt.Println()
		}
		fmt.Println(t)
	}
}

func sqrtInt(i int) int {
	return int(math.Sqrt(float64(i)))
}

func calc(checkSlice []string, l int, count int) ([]string, bool) {
	size := sqrtInt(len(goal))
	if l > count {
		return checkSlice, false
	}
	q := strings.Split(checkSlice[len(checkSlice)-1], "")
	index := slices.Index(q, "0")
	rows, columns := index/size, index%size

	// 0を動かす方向 上右下左 0123
	// 移動できる方向のスライスを作る
	movementSlice := []int{}

	if rows == 0 {
		movementSlice = append(movementSlice, 2)
	} else if rows == size-1 {
		movementSlice = append(movementSlice, 0)
	} else {
		movementSlice = append(movementSlice, 0, 2)
	}

	if columns == 0 {
		movementSlice = append(movementSlice, 1)
	} else if columns == size-1 {
		movementSlice = append(movementSlice, 3)
	} else {
		movementSlice = append(movementSlice, 1, 3)
	}

	for _, move := range movementSlice {
		if len(checkSlice) > l {
			checkSlice = checkSlice[:l]
		}
		after := -1
		// 0を動かす方向 上右下左 0123
		if move == 0 {
			after = index - size
		} else if move == 1 {
			after = index + 1
		} else if move == 2 {
			after = index + size
		} else if move == 3 {
			after = index - 1
		}
		q[index] = q[after]
		q[after] = "0"

		s := strings.Join(q, "")

		if slices.Contains(checkSlice, s) {
			q[after] = q[index]
			q[index] = "0"
			continue
		}

		checkSlice = append(checkSlice, s)

		if s == goal {
			return checkSlice, true
		}

		checkSlice, ok := calc(checkSlice, l+1, count)

		if ok {
			return checkSlice, true
		}

		q[after] = q[index]
		q[index] = "0"

	}
	return checkSlice, false
}

func repeatShuffle(count int) string {
	q := strings.Split(goal, "")

	prev := 0
	for range count {
		q, prev = shuffle(q, prev)
	}
	return strings.Join(q, "")
}

func shuffle(s []string, prev int) ([]string, int) {
	size := sqrtInt(len(goal))
	index := slices.Index(s, "0")
	rows, columns := index/size, index%size

	// 0を動かす方向 上右下左 0123
	// 移動できる方向のスライスを作る。
	movementSlice := []int{}

	if rows == 0 {
		movementSlice = append(movementSlice, 2)
	} else if rows == size-1 {
		movementSlice = append(movementSlice, 0)
	} else {
		movementSlice = append(movementSlice, 0, 2)
	}

	if columns == 0 {
		movementSlice = append(movementSlice, 1)
	} else if columns == size-1 {
		movementSlice = append(movementSlice, 3)
	} else {
		movementSlice = append(movementSlice, 1, 3)
	}

	// 来た方向に戻さない
	i := (prev + 2) % 4

	ii := slices.Index(movementSlice, i)
	if ii > -1 {
		movementSlice = slices.Delete(movementSlice, ii, ii+1)
	}

	// 動く方向を決める
	move := movementSlice[rand.Intn((len(movementSlice)))]

	after := -1
	// 0を動かす方向 上右下左 0123
	if move == 0 {
		after = index - size
	} else if move == 1 {
		after = index + 1
	} else if move == 2 {
		after = index + size
	} else if move == 3 {
		after = index - 1
	}
	s[index] = s[after]
	s[after] = "0"
	return s, move
}

表示は

shuffle: 7
count: 10
q.
q 412
q 753
q 806

ans.
0 412
0 753
0 806

1 412
1 753
1 086

2 412
2 053
2 786

3 012
3 453
3 786

4 102
4 453
4 786

5 120
5 453
5 786

6 123
6 450
6 786

7 123
7 456
7 780

556.5µs

こんな感じで。

試しにやってみたスマホアプリのくじ付きスライドパズルは僕の環境だと3x3は時間内に回答が出て入力できるけれど、4x4は時間内に回答が出ませんでした。