「プログラミング」に関する日記

プログラミング関係の話題。プログラミングコーナーを更新したとか、プログラミングの内容に踏み込んだ話とか。

次>

美文のキャラ倉庫と浮き沈みランチャー2とアンパンマンDB

美文のキャラ倉庫

様子見ながら記事いくつか更新。
修正が必要ない記事なら「移行作業中」カテゴリを外すだけなので、今後はもっと早く多く修正できると思います。
一応こうやって更新情報は列挙できるけど、一度に多くに記事を修正することになるし、一つ一つの細かい報告はしないほうがよさそうですね。

浮き沈みランチャー2

とりあえず、軽めだけど見た目の印象が大きく変わる2件を対応。

完了を待たずに閉じる件は、以下のように、明示的にHideしてから、投げっぱなしasyncしています。

private void OnContextRunAsAdminClicked(object? sender, RoutedEventArgs e)
{
    if (GetSelectedItem() is { } item && DataContext is MainViewModel mainVm)
    {
        Hide();
        mainVm.ExecuteItemAsync(item, runas: true);
    }
}
internal async void ExecuteItemAsync(Item item, bool runas = false)
{
    await Task.Run(() => ExecuteItemCore(item, runas));
}

private void ExecuteItemCore(Item item, bool runas = false)
{
    // 省略
}

スクロールのほうは、単純にScrollViewerで囲っているだけです。

<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" AllowAutoHide="False">
  <!-- 省略 -->
</ScrollViewer>

アンパンマンDB(記事更新)

来週の放送情報。
公式サイトは「フルーツのしま」名義だったけど、前例や放送された次回予告からすると、「フルーツ島」のほうが正しいはず。
確実な正解は次週を待て!

kawax/niconicoとcurl

kawax/niconico

修正自体は一瞬だったけど、プルリク受け付けない設定になってた!
AI Slop(AIによる低品質な量産品)対策でGitHubに搭載されたとは聞いていたけど、ここで出会うことになるとは。
メールは通じそうだったので、そちらで相談しています。

curl

で、念のためテストも回したんだけど、「cURL error 60: SSL certificate problem: unable to get local issuer certificate」というエラーが。
PHPに同梱されている証明書が若干古いのか、新しい証明書のhttpsにはつながらないみたいだったんですね。
curlのCA bundleから最新の証明書落としてきてPHPのcurlに登録して解決。

美文のキャラ倉庫とkawax/niconicoと日記

美文のキャラ倉庫

基本データ表示のためのLuaスクリプトほぼできた!
どうせサイトを公開したらスクリプトも全体公開になるのでソースコード置いときます。

(長いから折りたたむよ)

Module:BasicData

local p = {}

function p.process(frame)
    local args = frame:getParent().args
    local result = p.generate(args)

    return frame:preprocess(result)
end

function p.generate(args)
    local string_utils = require('Module:StringUtils')
    
    local fields = {
        { key = 'ふりがな', val = args['ふりがな'] },
        { key = '正式名称', val = args['正式名称'] },
        { key = 'フルネーム', val = args['フルネーム'] },
        { key = '英字表記', val = args['英字表記'] },
        { key = '他の名前', val = args['他の名前'] },
        { key = '公式サイト', val = args['公式サイト'] },
        { key = '登場作品', val = args['登場作品'] },
        { key = '種族', val = args['種族'] },
        { key = '性別', val = args['性別'] },
        { key = '年齢', val = args['年齢'] },
        { key = '大きさ', val = args['大きさ'] },
        { key = '一人称', val = args['一人称'] },
        { key = '二人称', val = args['二人称'] },
    }
    
    local info_rows = {}
    local cargo_rows = {}

    for _, field in ipairs(fields) do
        -- 値が存在し、空文字列でない場合のみ行を生成
        if field.val and field.val ~= '' then
            local key = field.key
            local value = field.val
            if key == 'ふりがな' then
                local sortkey = string_utils.sortkey(value)
                table.insert(cargo_rows, string.format('Furigana = %s', value))
                table.insert(cargo_rows, string.format('FuriganaSort = %s', sortkey))
                if args['表示用ふりがな'] and args['表示用ふりがな'] ~= '' then
                    value = args['表示用ふりがな']
                end
                value = string.format('%s{{DEFAULTSORT:%s}}', value, sortkey)
            elseif key == '登場作品' then
                local titles = mw.text.split(value, '、', true)
                local converted_titles = {}
                for _, title in ipairs(titles) do
                    if title == 'ふにゃ(ゲーム)' then
                        table.insert(converted_titles, '[[ふにゃ(ゲーム)|ふにゃ]][[Category:登場作品:ふにゃ]]')
                    else
                        table.insert(converted_titles, string.format('[[%s]][[Category:登場作品:%s]]', title, title))
                    end
                end
                value = table.concat(converted_titles, '、')
            elseif key == '種族' then
                local kinds = mw.text.split(value, '、', true)
                local converted_kinds = {}
                for _, kind in ipairs(kinds) do
                    table.insert(converted_kinds, string.format('[[%s]][[Category:種族:%s]]', kind, kind))
                end
                value = table.concat(converted_kinds, '、')
            elseif key == '性別' then
                value = string.format('%s[[Category:性別:%s]]', value, value)
            end
            table.insert(info_rows, string.format('<tr><th>%s</th><td>%s</td></tr>', key, value))
        end
    end


    local result = ""
    
    if next(info_rows) then
        result = string.format("%s<table class=\"wikitable\">%s</table>", result, table.concat(info_rows, ""))
    end
    
    if next(cargo_rows) then
        result = string.format("%s{{#cargo_store:_table = BasicData|%s}}", result, table.concat(cargo_rows, "|"))
    end

    return result
end

return p

Module:StringUtils

local p = {}

function p.sortkey(furigana)
    if furigana == "" then return "" end

    local sortkey_table = {
        {"が", "か"}, {"ぎ", "き"}, {"ぐ", "く"}, {"げ", "け"}, {"ご", "こ"},
        {"ざ", "さ"}, {"じ", "し"}, {"ず", "す"}, {"ぜ", "せ"}, {"ぞ", "そ"},
        {"だ", "た"}, {"ぢ", "ち"}, {"づ", "つ"}, {"で", "て"}, {"ど", "と"},
        {"ば", "は"}, {"び", "ひ"}, {"ぶ", "ふ"}, {"べ", "へ"}, {"ぼ", "ほ"},
        {"ぱ", "は"}, {"ぴ", "ひ"}, {"ぷ", "ふ"}, {"ぺ", "へ"}, {"ぽ", "ほ"},
        {"ゔ", "う"},
        {"ぁ", "あ"}, {"ぃ", "い"}, {"ぅ", "う"}, {"ぇ", "え"}, {"ぉ", "お"},
        {"ゃ", "や"}, {"ゅ", "ゆ"}, {"ょ", "よ"}, {"っ", "つ"}, {"ゎ", "わ"},
        {"あー", "ああ"}, {"いー", "いい"}, {"うー", "うう"}, {"えー", "ええ"}, {"おー", "おお"},
        {"かー", "かあ"}, {"きー", "きい"}, {"くー", "くう"}, {"けー", "けえ"}, {"こー", "こお"},
        {"さー", "さあ"}, {"しー", "しい"}, {"すー", "すう"}, {"せー", "せえ"}, {"そー", "そお"},
        {"たー", "たあ"}, {"ちー", "ちい"}, {"つー", "つう"}, {"てー", "てえ"}, {"とー", "とお"},
        {"なー", "なあ"}, {"にー", "にい"}, {"ぬー", "ぬう"}, {"ねー", "ねえ"}, {"のー", "のお"},
        {"はー", "はあ"}, {"ひー", "ひい"}, {"ふー", "ふう"}, {"へー", "へえ"}, {"ほー", "ほお"},
        {"まー", "まあ"}, {"みー", "みい"}, {"むー", "むう"}, {"めー", "めえ"}, {"もー", "もお"},
        {"やー", "やあ"},                   {"ゆー", "ゆう"},                   {"よー", "よお"},
        {"らー", "らあ"}, {"りー", "りい"}, {"るー", "るう"}, {"れー", "れえ"}, {"ろー", "ろお"},
        {"わー", "わあ"}, {"ゐー", "ゐい"},                   {"ゑー", "ゑえ"}, {"をー", "をお"},
    }

    for i, v in pairs(sortkey_table) do
        local from = v[1]
        local to = v[2]
        furigana = string.gsub(furigana, from, to)
    end
    
    return furigana
end

return p

まあ、おおむね引数で渡された項目を表に表示して、一部項目に特殊対応を入れている程度です。
ソートキー生成は、アンパンマンDB用にPHPで作っていたものをLuaに移植したもの。
文字列連結を繰り返すと遅くなるらしいので、ある程度はテーブルやstring.formatを使って連結を減らしています。
どこまで効果あるのか知らないですけどね。

欲を言えば、年齢や大きさも特殊対応入れたい。
昔作ったC#用単位・数値変換クラスをLuaに移植して、年齢順一覧とか大きさ順一覧とか作りたいんですよね。
ただ、ここで欲張ると公開がさらに先になっちゃいますからね。

kawax/niconico(TGWS内部処理)

PHPのバージョンを8.5に上げたいとか言ってましたが、思わぬところで問題が出ました。

kawax/niconico: ニコニコ動画API

ニコニコ動画の情報取得に使ってるこのライブラリ、誤動作とか致命的なエラーはないんですが、PHP8.4でDEPRECATEDが出るようになったのです。
場所を修正方法は把握していて、niconico/src/Search/Query.phpの16行目と、同29行目のパラメータです。
これが、null許可しないと明示されているのに初期値null扱いされているので、将来のバージョンでエラーにするから今のうちに直しとけよという警告を出してくれているというわけです。
昔のバージョンでは、null許可すると明示しなくても初期値nullにしておけば、暗黙的にnull許可扱いにしてくれていたんですが、それをやめることになったんですね。
だから、修正方針としては以下のように、型指定に「?」を付けて、null許可を明示します。

-    public function __construct(array $query = null)
+    public function __construct(?array $query = null)
-    public static function create(array $query = null): static
+    public static function create(?array $query = null): static

…というIssueとプルリクを明日出しとこう。そうしよう。

日記

さっきDIFFっぽいもの書きましたが、DIFFとしてのシンタックスハイライトは入れていません。
「```diff-php」とやればPHPのDIFFが出ると期待したんですが、コードブロック機能が壊れてしまいました。
なので、今は「```php」として、普通のPHPコード扱いでシンタックスハイライトを入れています。
今使っているシンタックスハイライトがDIFFに対応しているのかはわからないのですが、コードブロック機能が壊れるのは避けたほうがよさそうな気がします。
このコードブロック機能は私が作ったTgwsMarkの機能なんで、Issueとプルリクというより、自分で直すべき案件ですね。

バイオハザード レクイエムとAmazonストアフロントとQiita

バイオハザード レクイエム



動画投稿しました。
内容は同一です。
ゲーム音声と実況音声の音量バランスは調整したけど全体の音量調整してなかったなァ。
前回は出力設定で無茶して紙芝居になったし、動画作成手順をまとめておかないと。

Amazonストアフロント

アンパンマンDBから爆速で商品情報入れる方法思いついたので試してみた…けど、半分しか保存されてなかった!
どうやらこまめに確定させていく必要があるみたいだ。

Qiita

昔書いた記事の続編、今更書きました。
今アンパンマンDBで使っているコードほぼそのままです。
CSS、今も発展を続けているはずだけど、このCSSももっといい書き方が生まれてるのかなぁ。

あと、もう一つ別の記事も執筆中。
.NETです。

TGWS内部処理とアンパンマンDBとGitHub Copilot

TGWS内部処理

バイオ9の画像ページみたいに、たくさんある画像をいい感じに一覧表示する機能の、もっといい感じにやるやつ実装中。
ユーザー側からはあまり意識しないかと思いますが、URLが内部形式むき出しになっていたり、ページを最初に表示したときは表示しない画像も一気に生成するから重くなっていたりして、微妙だったんですよね。
なので、URLをすっきりした形に整理したり、必要な画像だけ作るようにしたり、ついでに画像形式をJPEGより高効率なWEBPにしたりしています。
いやあ、他でもないバイオ9の画像が、4K画像大量表示だから重いんですよね。

アンパンマンDB(アイディア)

トップページにもアフィリエイト入れようかなとか考え中。
別に生活が変わるほど売り上げがあるわけじゃないけど、売り上げが少なすぎるとAPI止められる可能性があるんですよね。
ただ、今でもトップページはかなりごちゃついているので、必ずやると決めているわけでもないです。

GitHub Copilot


記事執筆ではなくネタ出しのほうにAIを使うという案。
そもそも何書くか決まらないというのが一番困りますからね。

アンパンマンDBとRector

アンパンマンDB(記事更新)

来週の放送情報。
ガレットさん着実に登場回数重ねてる?

アンパンマンナイトの情報が出たので。

Rector(Today I Learned)

Rector: Fast PHP Code Upgrades & Refactoring
PHP向けの静的解析。
エラーにはならずとも、リスクになるコードとか、きれいに書けるところなどを徹底的に見つけ出してくれます。
で、修正までしてくれる優れものなのですが、1400も修正箇所が出てきて、さすがにこれを丸投げするのは怖いなということで、--dry-runで様子を見ながら手作業で修正しています。
言うて新機能が追加されるわけでもなけりゃバグが直るわけでもないわりに修正ミスで壊れる可能性があるから労力掛けてまで頑張る必要ないかもしれんな。
古いコードの温床となってるWikiのWordPressへの移行のほうが先でいいかも。

アンパンマンDBとドラゴンクエスト10とQiita

アンパンマンDB(記事更新)

来場者特典とキャンペーン情報を追記しました。

ドラゴンクエスト10(しろいコキン)

レギュラーダブルバラエティボックス
『ドラゴンクエスト』×『サーティワンアイスクリーム』 コラボ記念プレゼント (2026/5/27)|目覚めし冒険者の広場
サーティワンコラボのアイテム、アイス喰ってなくてももらえると今更気付いたので、貰ってきました。

Qiita

Avalonia UIのメニューに択一チェック
昨日言ってた別件の記事。
後は…そうね、ストアアプリ起動の記事ぐらいかな、ネタあるの。
実はさらに1個、古い環境をどうにかする系の記事も書きかけているんですが、セキュリティ上の懸念があるんじゃないかと思って公開できないんでいるんですよね。
いや、セキュリティの理由で書けてない記事はもう2つあるな…。

浮き沈みランチャー2とQiita

浮き沈みランチャー2

旧アプリでもできていたことの対応。

後はリストをエクスポートコンテキストメニューのタグ編集ができるようになれば旧アプリでできていたことは全部できるようになるかな。

Qiita

本当は別件の記事を投稿する予定だったのですが、今のライセンス無くなるでってメールが来たんで、その対応を記事に書きました。

以前書いた記事、ちょっと文面とタグを修正しました。

Amazon Creators APIとアンパンマン日替わり紹介bot

Amazon Creators API

新APIへの移行作業中。
いきなり書き換えるのではなく、共通インターフェースを持つラッパーを作って、元の機能を旧APIラッパーで置き換えて、新APIラッパーができたら切り替えるという方針で作っています。
まあ、いきなり新APIラッパーを使ったりしているのでこの方針が結局意味あるのかは微妙ですが。

アンパンマン日替わり紹介bot

アンパンマン日替わり紹介bot月別トップ(2026年) | アンパンマンDB
2026年分更新しました。
4月はまだデータが出そろっていないので暫定です。

UseLayoutRounding(Today I Learned)

FrameworkElement.UseLayoutRounding プロパティ (System.Windows) | Microsoft Learn
WPFでUI要素をピクセル境界に合わせるSnapsToDevicePixelsプロパティがうまく効いてない場所があるなーと思って調べていたら、似た役割のUseLayoutRoundingを見つけました。
SnapsToDevicePixelsは指定した要素自身をピクセル境界に合わせ、UseLayoutRoundingは子要素のレイアウトをピクセル境界に合わせるということかな。
ただ、子要素の相対位置をピクセル単位に合わせるという動作らしく、指定した要素自身がピクセル境界からずれていると意味がないようです。
だから、Windowとかの一番外側の要素に指定して、あとは余計な指定をせず継承するに任せておくという運用がいいのかな。
WPF版ふにゃでもぼやける部分があったんで、これで対策できないか試してみたいところです。

キャラ倉庫とWindowsアイコン

キャラ倉庫

エーさん
久しぶりにキャラ追加。
今のところ、登場作品はおろか、登場予定作品すらないです。

Windowsアイコン

Windowsで使用されるアプリケーションアイコンの画像サイズ
浮き沈みランチャー2では適切なサイズのアイコンを読み込む機能を実装予定なので、いろんなサイズを内包したわかりやすいアイコンが欲しいなーと思っていたらこんなのが。
代表的な各サイズのアイコンを、わかりやすく文字で表したものがありました。
ライセンスとか書いてないし配布とも言ってないのでうちのリポジトリに含めるのはやめとくけど、テスト用には充分使えそう。

PHP 8.4とプリンセスピーチ Showtime!とカービィのエアライダー

PHP 8.4

試しに検証環境で8.4を動かしてみて、致命的なエラーはなさそうなんですが、ちょっとDeprecatedが出てるので、そのあたりどうにかするためにCSV周り調整中。
標準関数の厄介ごとに巻き込まれたくないので、league/csvを使う方針です。

プリンセスピーチ Showtime!


ぽこ あ ポケモン気になるなーって思ってたけど、先に今持ってるゲームのほうを心残りがないように終わらせちゃおうということで、ちょっと進めました。
今回は人魚のキラリスタのステージ。
ある種の山場と言えるのがキラリスタ救出ステージなんだけど、今回は敵が出るでもなく長丁場になるわけでもなく、結構あっさりしていました。
こっちが飛ばした音符の魚を跳ね返してきたし、キラリスタが事実上のボスだったのでは!?

キラメキストーンコンプにこだわる必要はないかなって思いはしたけど取り逃しがわかりやすかったのと一周が短かったので、一応2周目で全部取っときました。

他の写真は以下から。
プリンセスピーチ Showtime!(2026/03/05)の写真

カービィのエアライダー


心残りって程でもないけどエアライダーもタイムアタックとかやってないコースがあったので。
クリアチェッカーはコンプ済みなので、今後もこだわらない程度にちびちびやっていこうかと。

ドラゴンクエスト10のプレイヤーイベントを検索および通知するサイトとAmazon Product Advertising API

ドラゴンクエスト10のプレイヤーイベントを検索および通知するサイト

ログイン機能ほぼ実装完了。
まあ、動作チェックと細かい調整はまだ必要かな。

Amazon Product Advertising API

Creators API
どうにもPA-APIが終わってCreators APIに切り替わるらしい。
PA-APIのPHP用SDKは最新PHPへの対応が微妙だから、どのみち新しいのがするのがいいかな

ズートピア2とResend

ズートピア2


映画『ズートピア2』公式サイト|ディズニー公式
1見てないのに2観てきました。
こういうのファインディングドリーでもやったな。
1がジュディとニックが正式にバディとなるまでの話で、2が正式にバディとなった後の話かな。
基本的ににぎやかで愉快な雰囲気を貫きつつ、仲間だからって同じようにしなくてもいいみたいなテーマ性ははっきり真剣に描いている感じでしたね。

Resend(Today I Learned)

Resend · Email for developers
開発者向けメール送信サービス。
「開発者向けメール」送信サービスじゃなくて、「開発者向け」メール送信サービスね。
今うちのサイトではけっこう古いライブラリ使ってウェブサーバー内のsendmailで送っているので、セキュリティとか機能とか到達しやすさとかを考えると、乗り換えたほうがいいんじゃないかなーとか思うんですよね。

バリューサーバーとAvalonia UI

バリューサーバー

ソフトウェアメンテナンス | メンテナンス・障害 | Value Domain(バリュードメイン)
2月にPHP8.4がうちのサーバーにもついに搭載されるようです。
既にPHP8.5も正式リリースされているので周回遅れ感はあるのですが、Deprecated属性が使えるようになるのは個人的に熱い!
結構既存の機能をDeprecated扱いにして段階的に削ったりしていましたからね。
私の作業のスケジュールとしては、手元のコードを8.3/8.4両対応にして自動テストを回しつつ、正式搭載後に切り替えて段階的に8.3のコードを置き換えていくことになるかな。

Avalonia UI

NuGet Gallery | Avalonia.ReactiveUI 11.3.8
浮き沈みランチャー2用に使おうとしていて、何もコーディングが進まないままほったらかしている間に、Avalonia.ReactiveUIが非推奨になっていました。
で、今後何を使うべきなのかというと、ReactiveUI.Avalonia
同じじゃないですか!!

とりあえず、NuGetで入れなおして名前空間を入れ替えるだけで現状は済みそうでした。
開発が進む前に気付いてラッキーだったのか、前提が変わるまで進まなかったのがだらしないのか…

Qiitaとカービィのエアライダーとドラゴンクエスト10

Qiita

私がXServerを選ばなかった理由
無事公開されました。

カービィのエアライダー


オンラインとエアライドのクリアチェッカー埋め。
壊れかけの状態でゴールするお題に壊れやすいペーパースターで挑んだら、壊れかけを通り越して一撃死しました。
この後、タフなバトルチャリオットで場外ダメージを使ってダメージ調整し、クリアしました。


面白いのと言えば、コックカワサキで攻撃用の料理を作って自爆するやつ。
狭い通路で雑に乱打すれば確率でどうにかなります。
このお題のときにブルタンクを初めて長距離で使ってみたんですが、めちゃくちゃ癖が強い…というか、私自身の操作も癖が強かったですね。
ドリフト中に無意識にスティックを斜め後ろに倒してしまって、必要以上に減速しちゃってたんです。
それに気付くまでは、調子よく1位を走っていたと思ったら急にノロノロ運転になって最下位に転落するなんてことが何度もありました。
ドリフトは原則を抑えて曲がる走法なんだから、スティック倒す方向を意識するようにしたら、ずっと調子よく走れるようになりました。

(他プレイヤーの名前が出るので一応畳み)


オンラインはパドックでやってたんですが、誰でも入れる設定にしていたら、今日は何回か人が入ってきました。
何度かやり取りしてみて思ったけど、ゲーム内のメッセージ機能だけだと意思疎通満足にできないですね。
一応、やめる・続けるぐらいの意思表示はできるけど、どのモードで遊びたいかも聞けない。
人と遊ぶのも楽しいし、今日はいい人ばかり来てくれたけど、配信とかボイスチャットとかで繋がりながらじゃないとパドックは厳しいかも。
今後はとりあえずパスコード付けて一人パドックにしよう。


他にはこんな感じで一気に敵を倒す系のお題やったり。
クラッシュとかボムとかの一瞬で広範囲爆撃する技を、どこで使うのかというのがポイントでしたね。
爆発できるタイミングで周りに誰もいなくて結構周回数かかってしまいました。
残すところオンラインは3つ、基本の3モードはそれなりに残ってるけどオフラインでゆっくり埋めていけばいいかな。

他の写真は以下から。いつものごとく他のクリアチェッカー写真とかもあるよ。
カービィのエアライダー(2025/12/21)の写真

ドラゴンクエスト10(しろいコキン)

帝国の闇サンタたち

  • クエスト「もうひとつの星夜祭2」をクリアした!

イベント始まって一週間だしボスも伏せなくていいか!
しろいコキンもクリアしました。

ジングルジャケットセット(うしろ)
ァォィョッュは帽子が髪に埋まっちゃったけど、しろいコキンはまあまあいい感じ。
ポニテが収まるのか出るのかはっきりしないのは微妙だけど。

他の写真は以下から。
写真置き場「2025/12/21」

ゲーム記とQiita

ゲーム記

Nintendo Switch 2の画面写真がキャッシュ効いてなかったので、効くようにしました。
初回アクセス時は実感ないかもしれませんが、ページを戻ったり進んだりして同じページを開きなおしたときなんかは結構わかりやすく変わっていると思います。

Qiita

AIで記事を書く趣味はありませんが、AIに助言を受けるのはありだと思っているので、ちょっとChatGPTくんに読んでもらって微調整していました。

次>