
はてなキーワード:constとは
dorawiiがタイトルに「dorawii」と入れなくなったので、本文にdorawiiがある投稿も非表示にする必要が出てきました。
ただAIに聞いたんだけど、CSSでは無理でJavaScriptならできると言われました。
そのJavaScriptを下に載せます。
// ==UserScript==
// @namespacehttp://tampermonkey.net/
// @version 0.1
// @description 本文に「dorawii」が含まれる投稿を非表示にする
// @matchhttps://anond.hatelabo.jp/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
//はてな匿名ダイアリーの一般的な投稿要素のクラスを仮定しています
const POST_SELECTOR = '.body .section'; // 例: .bodyクラスの子孫の.section要素
// すべての投稿要素を取得
const posts = document.querySelectorAll(POST_SELECTOR);
posts.forEach(post => {
//投稿内の本文が含まれる要素(ここでは投稿全体を本文と見なす)のテキストを取得
const textContent = post.textContent || post.innerText;
if (textContent.includes(KEYWORD)) {
post.style.display = 'none';
}
});
})();
自分はiPhoneなので、Makeoverというアプリを入れて、JSの部分に上のを貼り付けてます。
async function collectAllUrls(startUrl) {
consturls = [];
let nextUrl = startUrl;
while (nextUrl) {
const res = await fetch(nextUrl);
consthtml = await res.text();
constdoc = new DOMParser().parseFromString(html, "text/html");
constlinks =doc.querySelectorAll("div.section > h3 > a:first-child");
urls.push(...[...links].map(link =>link.href));
const nextLink = [...doc.querySelectorAll("a")].find(a => a.textContent.includes("次の25件>"));
nextUrl = nextLink ? nextLink.href : null;
console.log(nextUrl)
}
returnurls;
}
(async () => {
constallUrls = await collectAllUrls(window.location.href);
console.log("総件数:",allUrls.length);
await Promise.all(allUrls.map(url =>{console.log(url);
fetch('https://b.hatena.ne.jp/dorawii_bukuma/add.edit.json', {
method: 'POST',
headers: {
},
body: newURLSearchParams({
'url':url,
'private': '0',
'comment': '[dorawii]わしが書いた',
'post_twitter': '0',
'with_status_op': '1',
'from': 'web-confirm'
})
});
} ));
console.log("全送信完了");
})();
https://b.hatena.ne.jp/site/anond.hatelabo.jp/?sort=eid
途中でブクマ数増えなくなったんだよね。待機処理つけるべきだったか。
-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250823194237# -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKmbHwAKCRBwMdsubs4+SJC0AP0Q7RDjUSe8p2aNNKV0KLhlbhnTY+kD7uuWCS8yLJILDgEA2Sm4b1496jjyC0ue64hovLwS3C4dcF5r5TBMyfRifw8==zIYi-----ENDPGP SIGNATURE-----
https://profile.hatena.ne.jp/dorawii_bukuma/
はてなのサイト側で読み込まれているはずのrksトークンを生成する関数を直接叩く方法がどうしても分からず結局request処理を自分で書く方法ではなく自動でUI側の保存ボタンをクリックするという無難な方向に落ち着いた。
最初から後者の方法をとっていればもっと全然早く作れたのにというは所詮言い訳か。
とにかくスクリプトを公開しておく。
@echo off
cd /d "C:\Users\user\Documents\jsscript"
:: Nodeサーバーを別ウィンドウで起動
start /min "" noderun-batch-server.js
::Pythonサーバーを別ウィンドウで起動(hatenaserver配下)
start cmd /k ""python hatenaserver\server.py
{
"username": "",
"password": ""
}from flask import Flask, request,jsonify
importjson
importos
from hatena_client import HatenaClient
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
config_path =os.path.join(os.path.dirname(__file__), 'config.json')
withopen(config_path, encoding='utf-8')as f:
config =json.load(f)
@app.route('/bookmark', methods=['POST'])
def handle_bookmark():
data = request.json
url = data.get("url")
if noturl:
returnjsonify({"error": "MissingURL"}), 400
client = HatenaClient(config["username"],config["password"])
client.start_browser()
if notclient.login():
client.quit()
returnjsonify({"error": "Login failed"}),403
success =client.add_bookmark(url)
client.quit()
returnjsonify({"status": "ok" if success else "fail"})
if __name__ == "__main__":
app.run(port=12347)
// ==UserScript==
// @name自動セルクマ送信
// @namespace tampermonkey.net/
// @version 2025-08-07
// @descriptiontry totakeoverthe world!
// @authorYou
// @matchanond.hatelabo.jp/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
consturl = location.href;
if (!/^https:\/\/anond\.hatelabo\.jp\/\d+$/.test(url)) return;
const editLink = document.querySelector('a.edit');
if (!editLink) {
// 既に編集ページなので処理をスキップ
console.log('編集リンクが存在するため、スクリプトを終了します。');
return;
}
fetch('localhost:12347/bookmark', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body:JSON.stringify({url:url })
}).then(r =>console.log("通知成功")).catch(e =>console.error("通知失敗", e));
})();
-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250821192753# -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaKb0qwAKCRBwMdsubs4+SHfiAQDcXmTHBaZ5Zzr1KI/OxZ0xl69oevOdy1FXJYwYvsmo5AD/ZPtZiO1JgTDjm+27iymlkdzIXOIGWfC82UTr1mJ7EwU==YoV+-----ENDPGP SIGNATURE-----
今時点の使えそうな Sonnet4 を使ってコード生成とか業務でやる時にTypescript は案外うまくいかないことが多い。
UIとかシンプルなものであれば結構うまくいくけど、graphql,prisma みたいなところになると、token数すごくなるし結局完成しない。
この辺りが、なんとも小骨がひっかかるから。Typescriptの型ってやっぱりあれなのかと思って調べてもらったんだ。
↓↓↓↓↓↓↓
##ソフトウェア工学から見たTypeScriptの3つの根本課題
Web上の専門的な議論や論文では、TypeScriptの課題は主に以下の3点に集約されます。これらはすべて、JavaScriptという土台との不適合性に起因するものです。
1. 不健全な型システム (Unsound Type System)
ソフトウェア工学において、型システムの**「健全性(Soundness)」**とは、「コンパイル時に型エラーがなかったプログラムは、実行時に型エラーを起こさない」という保証を指します。
TypeScriptは、この健全性を意図的に放棄しています。
設計目標の不在:TypeScriptの公式ドキュメントには「健全であること」は設計目標ではないと明記されています。これは、JavaScriptとの互換性や開発者の利便性を優先した、根本的なトレードオフです。
具体的な不健全さ:
配列の扱い:string 型の変数に (string |number) 型の配列を代入できてしまうなど、配列の扱いに不健全な部分があります。これが原因で、実行時に数値を取り出してしまい、string型のメソッドを呼び出してエラーになる、といったことが起こり得ます。
any型と型アサーション:any型の存在や、開発者がコンパイラに「これはこの型で間違いない」と強制する型アサーション(as構文)は、健全性を根本から破壊する「抜け道」です。
関数の引数(Bivariance):関数の引数の型チェックが、他の多くの言語(反変)とは異なり、より緩いルール(双変)になっています。これも実用性を優先した結果、理論的な正しさを犠牲にしている例です。
学術的な観点では、この「不健全さ」はTypeScriptの型システムが持つ最大の弱点と見なされています。
2.構造的部分型(Structural Typing)の罠
TypeScriptは、クラス名などによらず「構造が同じなら同じ型」とみなす構造的部分型を採用しています。これはダックタイピングが主流のJavaScript文化に合わせた賢い選択ですが、ソフトウェア工学的にはいくつかの罠があります。
意図しない互換性: 全く異なる目的で作られた二つのオブジェクトが、偶然同じ構造を持っていたために、型チェックをパスしてしまうことがあります。これにより、ドメイン(業務領域)の意図とは無関係にコードが結合してしまい、予期せぬバグを生む原因となります。
過剰なプロパティの許容:constconfig: { port:number } = { port: 8080, host: 'localhost' }; のように、変数経由で代入すると、型に定義されていない余分なプロパティ(host)がエラーになりません。これは仕様ですが、開発者の意図しないデータが紛れ込むことを許容してしまい、セキュリティやバグのリスクに繋がります。
これは「TypeScriptの型は、データの『形状』を記述するが、その『意味』や『文脈』を保証しない」という根本的な限界を示しています。
3. 「漏れのある抽象化(Leaky Abstraction)」としての本質
Joel Spolskyが提唱した「漏れのある抽象化の法則」に倣えば、TypeScriptはまさにその典型例です。
TypeScriptは「静的型付け」という抽象化レイヤーを提供しますが、開発者は常にその下にあるJavaScriptの泥臭い現実(undefined, null, thisの挙動など)を意識し続けなければなりません。
抽象化の不徹底: 型を書いているときも、最終的にそれがundefinedになりうることや、thisが何を指すかを常に考えなければなりません。抽象化レイヤーが、下位レイヤーの詳細を隠蔽しきれていないのです。
摩擦コスト: この「漏れ」が、これまで議論してきた「Union地獄」や「境界での型変換の手間」といった、開発上の継続的な摩擦コストを生み出しています。
##結論
ソフトウェア工学的な観点から見ると、Web上の専門家の議論は我々の対話の結論を強く裏付けています。
TypeScriptの課題は、個別の機能の優劣ではなく、「健全性を犠牲にしてでも、JavaScriptという動的な土台との互換性を最優先した」という、その成り立ちそのものにあります。その結果として生じる「不健全さ」「構造的部分型の罠」「漏れのある抽象化」といった問題は、この言語を使い続ける限り、アーキテクトが向き合い続けなければならない、本質的なトレードオフなのです。
東京23区と周辺のいくつかの市で、東京選挙区での参政党の得票率がどんな要因と関係しているのか、軽い気持ちで回帰分析してみた。
変数の多重共線性とか処理はガバガバなので軽い気持ちで見てほしいんだが、ざっくりまとめると、「大学院卒業者の割合」が高い地域では得票率が低く、「役員の割合」が高い地域では逆に得票率が高い、という傾向がありそう。
使ったデータはNHKが出している投票所別の得票率。手入力なので誤りがあるかもしれない。
それに、東京都の人口統計と国勢調査(令和2年の)などから市区町村ごとの属性データをくっつけて、変数を一律で標準化したうえで回帰分析を行った。
都内の市区町村のうち、データが揃ってる27地域を対象にした(23区+町田・八王子・調布・西東京)。
20万人以上の市しか一部のデータが見つけられなくて、そこはごめんって感じ。
まず、説明変数を11個使って線形回帰分析をしたところ、決定係数は0.83(調整済み決定係数は0.71)だった。何を使ったかは後で。
そこから影響が特に大きそうな4変数(平均年齢、大学院卒業者割合、役員の割合、情報通信業の割合)に絞って分析し直すと、決定係数は0.73(調整済み決定係数は0.68)になった。
詳しくはこれ
国勢調査は5年に1回しかなくて、最新の結果が令和2年のだった。
4つの変数の関係を見てみると、平均年齢は他の3つの変数(大学院卒、役員、情報通信業)と負の相関を持っていた(相関係数 < -0.69)。一方、大学院卒業者の割合・役員の割合・情報通信業の割合は互いに中程度以上の正の相関(相関係数> 0.5)を持っており、特に大学院卒と役員の間の相関係数は0.75と大きかった(いずれもピアソン相関)。
ただし、回帰係数を見ると、興味深い違いがある。大学院卒業者の割合、平均年齢、情報通信業の割合はいずれも負の係数を持っていて、これらが高いと参政党の得票率は下がる傾向がある。一方で、役員の割合は正の係数を持っていた。
| 市区町村 | 参政党得票率(NHK) | 予測値_参政党得票率 | 平均年齢(令和7年1月) | 大学院卒業者割合(令和2年国勢調査) | 役員の割合(令和2年国勢調査) | 情報通信業の割合(令和2年国勢調査) |
| 千代田区 | 9.4 | 9.6 | 42.69 | 0.088 | 0.162 | 0.115 |
| 中央区 | 9.8 | 9.3 | 42.17 | 0.075 | 0.126 | 0.135 |
| 港区 | 10.1 | 10.4 | 43.48 | 0.065 | 0.171 | 0.131 |
| 新宿区 | 9.4 | 9.5 | 44.08 | 0.052 | 0.097 | 0.129 |
| 文京区 | 7.4 | 7.6 | 43.35 | 0.097 | 0.098 | 0.118 |
| 台東区 | 10 | 10.1 | 45.59 | 0.041 | 0.109 | 0.112 |
| 墨田区 | 10.1 | 9.8 | 44.88 | 0.035 | 0.073 | 0.115 |
| 江東区 | 9 | 9.4 | 44.82 | 0.041 | 0.069 | 0.12 |
| 品川区 | 9 | 8.6 | 44.34 | 0.056 | 0.077 | 0.143 |
| 目黒区 | 9 | 9.4 | 44.88 | 0.05 | 0.109 | 0.137 |
| 大田区 | 9.9 | 9.5 | 45.67 | 0.039 | 0.069 | 0.105 |
| 世田谷区 | 9.9 | 9.4 | 45.19 | 0.047 | 0.097 | 0.128 |
| 渋谷区 | 10 | 9.7 | 44.8 | 0.054 | 0.142 | 0.152 |
| 中野区 | 9.5 | 9.3 | 44.57 | 0.038 | 0.072 | 0.141 |
| 杉並区 | 8.5 | 8.9 | 45.23 | 0.047 | 0.076 | 0.136 |
| 豊島区 | 9.6 | 9.5 | 44.05 | 0.044 | 0.081 | 0.132 |
| 北区 | 9.2 | 9.4 | 45.74 | 0.036 | 0.058 | 0.107 |
| 荒川区 | 9.4 | 9.9 | 46.23 | 0.032 | 0.071 | 0.096 |
| 板橋区 | 9.9 | 10.0 | 45.73 | 0.027 | 0.059 | 0.099 |
| 練馬区 | 10.3 | 9.6 | 45.5 | 0.034 | 0.068 | 0.113 |
| 足立区 | 10.5 | 10.7 | 46.74 | 0.017 | 0.063 | 0.073 |
| 葛飾区 | 10 | 10.4 | 46.52 | 0.02 | 0.061 | 0.083 |
| 江戸川区 | 11 | 10.7 | 45.09 | 0.021 | 0.062 | 0.085 |
| 八王子市 | 10.1 | 9.7 | 48.31 | 0.029 | 0.054 | 0.054 |
| 町田市 | 10 | 9.5 | 48.16 | 0.031 | 0.058 | 0.068 |
| 調布市 | 8.6 | 9.4 | 45.66 | 0.035 | 0.06 | 0.113 |
| 西東京市 | 9.1 | 9.5 | 46.9 | 0.028 | 0.055 | 0.102 |
雑なモデルなので話半分でね。
OLS Regression Results ==============================================================================Dep. Variable: y R-squared: 0.730Model:OLS Adj. R-squared: 0.680Method: Least Squares F-statistic: 14.84Date: Mon, 21 Jul2025 Prob (F-statistic): 5.09e-06Time: 07:21:02Log-Likelihood: -20.653No. Observations: 27AIC: 51.31Df Residuals: 22BIC: 57.78DfModel: 4 Covariance Type: nonrobust ============================================================================== coefstd err tP>|t| [0.025 0.975]------------------------------------------------------------------------------const 1.277e-15 0.111 1.15e-14 1.000 -0.230 0.230x1 -0.5743 0.230 -2.493 0.021 -1.052 -0.096x2 -1.3278 0.204 -6.512 0.000 -1.751 -0.905x3 0.8670 0.174 4.973 0.000 0.505 1.229x4 -0.5382 0.169 -3.184 0.004 -0.889 -0.188==============================================================================Omnibus: 2.233 Durbin-Watson: 2.170Prob(Omnibus): 0.327 Jarque-Bera (JB): 1.169Skew: -0.035 Prob(JB): 0.557Kurtosis: 1.983 Cond. No. 4.48==============================================================================
| 変数 | 回帰係数 |
| 平均年齢(令和7年1月) | -0.78 |
| 1世帯あたり人口 | -0.31 |
| 男性率(令和7年1月) | 0.07 |
| 外国人比率(令和7年1月) | -0.07 |
| 5年間外国人割合変化 | 0.27 |
| 犯罪認知割合 | -0.05 |
| 大学院卒業者/全卒業者(令和2年国勢調査) | -1.77 |
| 不詳者/全卒業者(令和2年国勢調査) | -0.51 |
| 従業上の地位:役員の割合 | 1.39 |
| 従業上の地位:自営業主の割合 | 0.09 |
| 産業区分:情報通信業の割合 | -0.53 |
| 地域 | 参政党得票率(NHK) | 予測値_参政党得票率 | 平均年齢(令和7年1月) | 1世帯あたり人口 | 男性率(令和7年1月) | 外国人比率(令和7年1月) | 5年間外国人割合変化(令和2年から7年) | 犯罪認知割合(令和6年件数/令和7年人口) | 大学院卒業者/全卒業者(令和2年国勢調査) | 不詳者/全卒業者(令和2年国勢調査) | 従業上の地位:役員の割合(令和2年国勢調査) | 従業上の地位:自営業主の割合(令和2年国勢調査) | 産業区分:情報通信業の割合(令和2年国勢調査) |
| 千代田区 | 9.4 | 9.5 | 42.69 | 1.75 | 0.50 | 0.06 | 1.22 | 0.04 | 0.09 | 0.36 | 0.16 | 0.09 | 0.12 |
| 中央区 | 9.8 | 9.8 | 42.17 | 1.76 | 0.48 | 0.07 | 1.33 | 0.01 | 0.08 | 0.28 | 0.13 | 0.08 | 0.14 |
| 港区 | 10.1 | 10.0 | 43.48 | 1.74 | 0.47 | 0.08 | 1.08 | 0.01 | 0.07 | 0.42 | 0.17 | 0.10 | 0.13 |
| 新宿区 | 9.4 | 9.0 | 44.08 | 1.52 | 0.50 | 0.14 | 1.12 | 0.02 | 0.05 | 0.39 | 0.10 | 0.09 | 0.13 |
| 文京区 | 7.4 | 7.5 | 43.35 | 1.80 | 0.48 | 0.07 | 1.32 | 0.01 | 0.10 | 0.25 | 0.10 | 0.08 | 0.12 |
| 台東区 | 10.0 | 10.3 | 45.59 | 1.58 | 0.51 | 0.09 | 1.21 | 0.01 | 0.04 | 0.36 | 0.11 | 0.09 | 0.11 |
| 墨田区 | 10.1 | 10.1 | 44.88 | 1.69 | 0.49 | 0.06 | 1.25 | 0.01 | 0.04 | 0.28 | 0.07 | 0.07 | 0.12 |
| 江東区 | 9.0 | 9.2 | 44.82 | 1.84 | 0.49 | 0.07 | 1.23 | 0.01 | 0.04 | 0.27 | 0.07 | 0.06 | 0.12 |
| 品川区 | 9.0 | 8.6 | 44.34 | 1.73 | 0.49 | 0.04 | 1.19 | 0.01 | 0.06 | 0.24 | 0.08 | 0.07 | 0.14 |
| 目黒区 | 9.0 | 9.3 | 44.88 | 1.74 | 0.47 | 0.04 | 1.19 | 0.01 | 0.05 | 0.35 | 0.11 | 0.10 | 0.14 |
| 大田区 | 9.9 | 9.7 | 45.67 | 1.77 | 0.50 | 0.04 | 1.26 | 0.01 | 0.04 | 0.23 | 0.07 | 0.07 | 0.11 |
| 世田谷区 | 9.9 | 9.3 | 45.19 | 1.84 | 0.47 | 0.03 | 1.22 | 0.01 | 0.05 | 0.30 | 0.10 | 0.10 | 0.13 |
| 渋谷区 | 10.0 | 9.9 | 44.80 | 1.61 | 0.48 | 0.06 | 1.12 | 0.02 | 0.05 | 0.34 | 0.14 | 0.12 | 0.15 |
| 中野区 | 9.5 | 9.5 | 44.57 | 1.57 | 0.51 | 0.07 | 1.20 | 0.01 | 0.04 | 0.33 | 0.07 | 0.09 | 0.14 |
| 杉並区 | 8.5 | 8.9 | 45.23 | 1.73 | 0.48 | 0.04 | 1.19 | 0.00 | 0.05 | 0.26 | 0.08 | 0.09 | 0.14 |
| 豊島区 | 9.6 | 9.5 | 44.05 | 1.57 | 0.50 | 0.12 | 1.21 | 0.01 | 0.04 | 0.34 | 0.08 | 0.09 | 0.13 |
| 北区 | 9.2 | 9.2 | 45.74 | 1.71 | 0.50 | 0.09 | 1.31 | 0.01 | 0.04 | 0.31 | 0.06 | 0.07 | 0.11 |
| 荒川区 | 9.4 | 9.6 | 46.23 | 1.77 | 0.50 | 0.11 | 1.19 | 0.01 | 0.03 | 0.29 | 0.07 | 0.08 | 0.10 |
| 板橋区 | 9.9 | 10.0 | 45.73 | 1.73 | 0.49 | 0.07 | 1.29 | 0.01 | 0.03 | 0.30 | 0.06 | 0.07 | 0.10 |
| 練馬区 | 10.3 | 9.6 | 45.50 | 1.89 | 0.48 | 0.04 | 1.22 | 0.01 | 0.03 | 0.25 | 0.07 | 0.08 | 0.11 |
| 足立区 | 10.5 | 10.6 | 46.74 | 1.84 | 0.50 | 0.06 | 1.28 | 0.01 | 0.02 | 0.31 | 0.06 | 0.08 | 0.07 |
| 葛飾区 | 10.0 | 10.5 | 46.52 | 1.86 | 0.50 | 0.06 | 1.27 | 0.01 | 0.02 | 0.27 | 0.06 | 0.08 | 0.08 |
| 江戸川区 | 11.0 | 10.8 | 45.09 | 1.93 | 0.50 | 0.07 | 1.27 | 0.01 | 0.02 | 0.26 | 0.06 | 0.07 | 0.09 |
| 八王子市 | 10.1 | 9.7 | 48.31 | 1.96 | 0.50 | 0.03 | 1.28 | 0.01 | 0.03 | 0.21 | 0.05 | 0.07 | 0.05 |
| 町田市 | 10.0 | 10.0 | 48.16 | 2.06 | 0.49 | 0.02 | 1.44 | 0.01 | 0.03 | 0.17 | 0.06 | 0.08 | 0.07 |
| 調布市 | 8.6 | 9.1 | 45.66 | 1.92 | 0.49 | 0.02 | 1.14 | 0.01 | 0.04 | 0.23 | 0.06 | 0.08 | 0.11 |
| 西東京市 | 9.1 | 9.2 | 46.90 | 2.00 | 0.49 | 0.03 | 1.15 | 0.01 | 0.03 | 0.20 | 0.06 | 0.08 | 0.10 |
OLS Regression Results ==============================================================================Dep. Variable: y R-squared: 0.833Model:OLS Adj. R-squared: 0.711Method: Least Squares F-statistic: 6.803Date: Mon, 21 Jul2025 Prob (F-statistic): 0.000472Time: 06:53:14Log-Likelihood: -14.148No. Observations: 27AIC: 52.30Df Residuals: 15BIC: 67.85DfModel:11 Covariance Type: nonrobust ============================================================================== coefstd err tP>|t| [0.025 0.975]------------------------------------------------------------------------------const -5.405e-15 0.106 -5.12e-14 1.000 -0.225 0.225x1 -0.7820 0.361 -2.165 0.047 -1.552 -0.012x2 -0.3056 0.355 -0.860 0.403 -1.063 0.452x3 0.0671 0.270 0.248 0.807 -0.509 0.643x4 -0.0737 0.213 -0.346 0.734 -0.527 0.379x5 0.2652 0.168 1.579 0.135 -0.093 0.623x6 -0.0534 0.246 -0.217 0.831 -0.578 0.472x7 -1.7650 0.293 -6.018 0.000 -2.390 -1.140x8 -0.5147 0.379 -1.358 0.195 -1.322 0.293x9 1.3916 0.348 3.994 0.001 0.649
<html lang="ja"><head> <meta charset="UTF-8"> <title>GrokのPONGゲーム</title> <style>body { display:flex; justify-content: center; align-items: center; height:100vh;margin: 0;background: #1a1a1a; }canvas {border: 2px solid #00ff00;background: #000; } #score {color: #00ff00; font-family: 'Courier New', monospace; font-size: 24px; position:absolute;top:20px; width:100%;text-align: center; } </style></head><body>0 : 0<canvasid="gameCanvas" width="800" height="400"></canvas> <script>constcanvas = document.getElementById('gameCanvas');const ctx =canvas.getContext('2d');constscoreDisplay = document.getElementById('score'); //ゲームオブジェクトconst ball = { x:canvas.width / 2, y:canvas.height / 2,radius:10, speedX: 5, speedY: 5 };const paddleLeft = { x:10, y:canvas.height / 2 - 50, width:10, height:100, speed: 8 };const paddleRight = { x:canvas.width -20, y:canvas.height / 2 - 50, width:10, height:100, speed: 8 }; letscoreLeft = 0,scoreRight = 0; //キー入力constkeys = { w:false, s:false, ArrowUp:false, ArrowDown:false }; document.addEventListener('keydown', e => { if (keys.hasOwnProperty(e.key))keys[e.key] =true; }); document.addEventListener('keyup', e => { if (keys.hasOwnProperty(e.key))keys[e.key] =false; }); //ゲームループ function gameLoop() { // 移動 if (keys.w && paddleLeft.y> 0) paddleLeft.y -= paddleLeft.speed; if (keys.s && paddleLeft.y <canvas.height - paddleLeft.height) paddleLeft.y += paddleLeft.speed; if (keys.ArrowUp && paddleRight.y> 0) paddleRight.y -= paddleRight.speed; if (keys.ArrowDown && paddleRight.y <canvas.height - paddleRight.height) paddleRight.y += paddleRight.speed; //ボール移動 ball.x += ball.speedX; ball.y += ball.speedY; // 壁衝突 if (ball.y + ball.radius>canvas.height || ball.y - ball.radius < 0) ball.speedY = -ball.speedY; //パドル衝突 if ( (ball.x - ball.radius < paddleLeft.x + paddleLeft.width && ball.y> paddleLeft.y && ball.y < paddleLeft.y + paddleLeft.height) || (ball.x + ball.radius> paddleRight.x && ball.y> paddleRight.y && ball.y < paddleRight.y + paddleRight.height) ) { ball.speedX = -ball.speedX * 1.05; // 少し加速 } //得点 if (ball.x < 0) {scoreRight++; ballReset(); } if (ball.x>canvas.width) {scoreLeft++; ballReset(); } // 描画 ctx.fillStyle = '#000'; ctx.fillRect(0, 0,canvas.width,canvas.height); ctx.fillStyle = '#00ff00'; ctx.fillRect(paddleLeft.x, paddleLeft.y, paddleLeft.width, paddleLeft.height); ctx.fillRect(paddleRight.x, paddleRight.y, paddleRight.width, paddleRight.height); ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); ctx.fill();scoreDisplay.textContent = `${scoreLeft} : ${scoreRight}`; requestAnimationFrame(gameLoop); } function ballReset() { ball.x =canvas.width / 2; ball.y =canvas.height / 2; ball.speedX = (Math.random()> 0.5 ? 5 : -5) * (Math.random() * 0.5 + 0.75); ball.speedY = (Math.random() * 4 - 2); } gameLoop(); </script></body></html>
<html lang="ja"><head> <meta charset="UTF-8"> <metaname="viewport" content="width=device-width, initial-scale=1.0"> <title>PONG Game</title> <style>body {margin: 0;padding: 0;background-color: #000; display:flex; justify-content: center; align-items: center;min-height:100vh; font-family: 'Courier New', monospace;color:white; }``` .game-container {text-align: center; }canvas {border: 2px solidwhite;background-color: #000; } .score { font-size: 24px;margin:20px 0; letter-spacing: 2px; } .controls {margin-top:20px; font-size: 14px; opacity: 0.8; } .start-button {background-color: #333;color:white;border: 2px solidwhite;padding:10px20px; font-size: 16px; cursor: pointer; font-family: 'Courier New', monospace;margin:10px; } .start-button:hover {background-color:white;color: black; }</style>```</head><body>```<script> //Canvas要素とコンテキストの取得constcanvas = document.getElementById('gameCanvas');const ctx =canvas.getContext('2d'); //ゲームの状態管理 let gameRunning =false; let animationId; //スコア要素の取得const playerScoreElement = document.getElementById('playerScore');constcomputerScoreElement = document.getElementById('computerScore'); //ゲームオブジェクトの定義const game = { //プレイヤーのパドル(左側) playerPaddle: { x:10, y:canvas.height / 2 - 50, width:10, height:100, speed: 5, upPressed:false, downPressed:false }, //コンピューターのパドル(右側)computerPaddle: { x:canvas.width -20, y:canvas.height / 2 - 50, width:10, height:100, speed: 3.5, //プレイヤーより少し遅く設定 targetY:canvas.height / 2 - 50 }, //ボールの設定 ball: { x:canvas.width / 2, y:canvas.height / 2,radius: 8, speedX: 4, speedY: 3, maxSpeed: 8 }, //スコアの管理score: { player: 0,computer: 0 } }; //キーボード入力の処理constkeys = {}; //キーが押されたときの処理 document.addEventListener('keydown', (e) => {keys[e.key.toLowerCase()] =true; //ゲームが停止中にスペースキーでゲーム開始 if (e.key === ' ' && !gameRunning) { startGame(); } }); //キーが離されたときの処理 document.addEventListener('keyup', (e) => {keys[e.key.toLowerCase()] =false; }); //パドルの移動処理 function updatePaddles() { //プレイヤーパドルの移動(W/Sキーまたは矢印キー) if (keys['w'] ||keys['arrowup']) { game.playerPaddle.y -= game.playerPaddle.speed; } if (keys['s'] ||keys['arrowdown']) { game.playerPaddle.y += game.playerPaddle.speed; } //プレイヤーパドルの画面外移動を防ぐ if (game.playerPaddle.y < 0) { game.playerPaddle.y = 0; } if (game.playerPaddle.y>canvas.height - game.playerPaddle.height) { game.playerPaddle.y =canvas.height - game.playerPaddle.height; } //コンピューターパドルのAI処理 //ボールの位置を追跡するが、完璧ではない動きを実装const ballCenterY = game.ball.y;const paddleCenterY = game.computerPaddle.y + game.computerPaddle.height / 2; //ボールとパドルの中心の差を計算constdifference = ballCenterY - paddleCenterY; // 反応に少し遅れを持たせる(人間らしい動き) if (Math.abs(difference)>10) { if (difference> 0) { game.computerPaddle.y += game.computerPaddle.speed; } else { game.computerPaddle.y -= game.computerPaddle.speed; } } //コンピューターパドルの画面外移動を防ぐ if (game.computerPaddle.y < 0) { game.computerPaddle.y = 0; } if (game.computerPaddle.y>canvas.height - game.computerPaddle.height) { game.computerPaddle.y =canvas.height - game.computerPaddle.height; } } //ボールの移動と衝突判定 function updateBall() { //ボールの位置を更新 game.ball.x += game.ball.speedX; game.ball.y += game.ball.speedY; //上下の壁との衝突判定 if (game.ball.y - game.ball.radius < 0 || game.ball.y + game.ball.radius>canvas.height) { game.ball.speedY = -game.ball.speedY; } //プレイヤーパドルとの衝突判定 if (game.ball.x - game.ball.radius < game.playerPaddle.x + game.playerPaddle.width && game.ball.x + game.ball.radius> game.playerPaddle.x && game.ball.y + game.ball.radius> game.playerPaddle.y && game.ball.y - game.ball.radius < game.playerPaddle.y + game.playerPaddle.height) { //ボールがパドルに当たった位置によって跳ね返り角度を調整const hitPos = (game.ball.y - (game.playerPaddle.y + game.playerPaddle.height / 2)) / (game.playerPaddle.height / 2); game.ball.speedX = Math.abs(game.ball.speedX); game.ball.speedY = hitPos * 4; //ボールの速度を少し上げる(ゲームをエキサイティングに) if (Math.abs(game.ball.speedX) < game.ball.maxSpeed) { game.ball.speedX *= 1.02; } } //コンピューターパドルとの衝突判定 if (game.ball.x + game.ball.radius> game.computerPaddle.x && game.ball.x - game.ball.radius < game.computerPaddle.x + game.computerPaddle.width && game.ball.y + game.ball.radius> game.computerPaddle.y && game.ball.y - game.ball.radius < game.computerPaddle.y + game.computerPaddle.height) { //ボールがパドルに当たった位置によって跳ね返り角度を調整const hitPos = (game.ball.y - (game.computerPaddle.y + game.computerPaddle.height / 2)) / (game.computerPaddle.height / 2); game.ball.speedX = -Math.abs(game.ball.speedX); game.ball.speedY = hitPos * 4; //ボールの速度を少し上げる if (Math.abs(game.ball.speedX) < game.ball.maxSpeed) { game.ball.speedX *= 1.02; } } //ボールが左右の壁を越えた場合(得点処理) if (game.ball.x < 0) { //コンピューターの得点 game.score.computer++; updateScore(); resetBall(); } else if (game.ball.x>canvas.width) { //プレイヤーの得点 game.score.player++; updateScore(); resetBall(); } } //ボールをリセット(得点後の処理) function resetBall() { game.ball.x =canvas.width / 2; game.ball.y =canvas.height / 2; //ランダムな方向でボールを発射 game.ball.speedX = (Math.random()> 0.5 ? 4 : -4); game.ball.speedY = (Math.random() - 0.5) * 6; } //スコア表示の更新 function updateScore() { playerScoreElement.textContent = game.score.player;computerScoreElement.textContent = game.score.computer; } // 描画処理 functiondraw() { // 画面をクリア ctx.fillStyle = '#000'; ctx.fillRect(0, 0,canvas.width,canvas.height); //中央の点線を描画 ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(canvas.width / 2, 0); ctx.lineTo(canvas.width / 2,canvas.height); ctx.strokeStyle = '#fff'; ctx.stroke(); ctx.setLineDash([]); //プレイヤーパドルを描画 ctx.fillStyle = '#fff'; ctx.fillRect(game.playerPaddle.x, game.playerPaddle.y, game.playerPaddle.width, game.playerPaddle.height); //コンピューターパドルを描画 ctx.fillRect(game.computerPaddle.x, game.computerPaddle.y, game.computerPaddle.width, game.computerPaddle.height); //ボールを描画 ctx.beginPath(); ctx.arc(game.ball.x, game.ball.y, game.ball.radius, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); //ゲームが停止中の場合、メッセージを表示 if (!gameRunning) { ctx.fillStyle = '#fff'; ctx.font = '20px Courier New'; ctx.textAlign = 'center'; ctx.fillText('ゲーム開始ボタンを押してください',canvas.width / 2,canvas.height / 2 + 60); } } //ゲームのメインループ function gameLoop() { if (!gameRunning) return; updatePaddles(); updateBall();draw(); animationId = requestAnimationFrame(gameLoop); } //ゲーム開始 function startGame() { gameRunning =true; gameLoop(); } //ゲームリセット function resetGame() { gameRunning =false; if (animationId) { cancelAnimationFrame(animationId); } //スコアをリセット game.score.player = 0; game.score.computer = 0; updateScore(); //ボールとパドルの位置をリセット game.ball.x =canvas.width / 2; game.ball.y =canvas.height / 2; game.ball.speedX = 4; game.ball.speedY = 3; game.playerPaddle.y =canvas.height / 2 - 50; game.computerPaddle.y =canvas.height / 2 - 50;draw(); } // 初期描画draw();</script>```</body></html>
ここで言う「プログラミング初級者」とはプログラミングの記述が上から下へ向かって順番に処理されること、条件分岐やループという概念があることを理解しており、RPGゲームが作れる「RPGツクール(現RPG Maker)」や学童向けプログラミング環境「Scratch」、「ナビつき! つくってわかる はじめてゲームプログラミング(ナビつく)」、ADVゲームが作れる「吉里吉里(もしくは吉里吉里2)」、過去にBASICやC、HSP、Javascriptあたりでプログラミングへ挑戦し挫折したなどなど、ある程度の「プログラマブルなロジック」構築の経験がある者を指します。
ある時、筆者はふと思いました。「生成AIはなんだかんだで膨大なテキスト情報を処理している事がキモだよなぁ」とありきたりなことを。
そして、同時にプログラミング初級者の弱点として「現在記述されているコードの管理においてテキストと実際の処理フローが脳内で一致しない」「プログラミング言語ごとに定められているルールや関数予約語の把握が困難」なのが問題とも考えました。
前述したプログラミング初級者の弱点の考え自体は車輪の再発明であり、「Scratch」や、より高度な「UML」が既に存在しており、特筆すべきことは何もありません。
しかし、「Scratch」や「UML」、なんなら「RPGツクール」や「吉里吉里」などに無い点として、現代では自然言語処理が大幅に向上した生成AIが実用の域にまで到達しつつあるのが従来とは異なる点でした。
つまり、自然言語を混ぜ込みやすいテキストベースの言語、かつ、処理を記述するとフローが視覚的に理解しやすい言語、可能であれば情報量が多くて一部の界隈で広く使われている言語があればプログラミング初級者も気軽にプログラミングできるのではないか?と発想しました。
コンピュータ(コンパイラやインタプリタなどソフトウェアを含む)が解することができる言語にはプログラミング言語以外にも様々あり、今回取り上げるのは「データ記述言語」と呼ばれるものです。
データ記述言語の中でもグラフ作成へ特化しており、特にフローチャート作成で真価を発揮する「DOT言語」というものがあります。
早速ですが、実際に手を動かしてみましょう。ちなみにDOT言語はGraphviz OnlineというWebツールがあるため別途に何かしらをインストールして環境構築する必要はありません。便利な世の中ですね。
上記のGraphviz Onlineを開くと、既に左側のDOT言語で記述された内容が、右側で作図されています。DOT言語はこのような図を作図するためのデータ記述言語です。
一旦、左側の記述をCtrl+Aで全選択をしDeleteなどで全削除し、下記の内容をコピペしてみましょう。
digraph graphname {
A -> B;
}
DOT言語の詳細な使い方は様々なWebサイトやブログ記事、Qiitaなどへ譲るとして、A - > Bの見た目から発想の転換をしてみると処理Aから処理Bという流れに見えませんか?
DOT言語は生成AIを利用する上で有利なテキストベースでありながらグラフを作成できるのがキモであり、例えばこのA -> BがA「Webページを開いたら」 → B「Hello, Worldと表示する」という風にできるのであれば処理のフローが可視化されており本当に素晴らしいことです。
ここでプログラミングの有識者は「DOT言語をUMLなどに見立てて処理を記述するのは良いが、プログラミング初心者は求めた結果を出力するロジックやアルゴリズムを発想する知見や経験値が圧倒的に足りていないのが問題ではないか?」と至極真っ当かつ反論の余地がない問題点の指摘をすると思いますが、そこで活きるのが生成AIです。
生成AIは初級者プログラマ個人のロジックやアルゴリズムの作成能力へ無関係にロジックやアルゴリズムを生成できる(可能性が少なくともある)ので、処理のフローをDOT言語で記述することにより処理フローから初級者プログラマが求めるプログラムを生成AIへ推論させ生成させるというのが狙いです。実際に試してみましょう。
digraph graphname {
A[
label = "Webページを表示する"
];
B[
label = "Webページを表示したらHello, worldとテキストを表示する"
];
A -> B;
}
プログラミングの有識者はこの時点で「なるほど、自然言語の記述をそのままDOT言語に入れ込んでしまうのか」と気付いたのではないかと思います。
このDOT言語をGoogle Gemini 2.5Flashへ「下記のデータ記述言語のDOT言語で記述された処理フローをJavascript言語へ変換をしWebページ上へ「Hello,world」を表示したいので変換をお願いします。」と命令すると以下のコードが生成されました。
<!DOCTYPEhtml>
<html lang="ja">
<head>
<meta charset="UTF-8">
<metaname="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World Page</title>
</head>
<body>
<divid="output"></div>
<script>
//Webページが表示されたら(DOMが完全に読み込まれたら)実行される
document.addEventListener('DOMContentLoaded', function() {
// "Webページを表示する"ステップは、このHTMLファイル自体が表示されることで達成されます。
// "Webページを表示したらHello, worldとテキストを表示する"ステップ
const outputDiv = document.getElementById('output');
outputDiv.textContent = 'Hello, world';
});
</script>
</body>
</html>
フローを記述する利点は、ロジックやアルゴリズムを発想する知見や経験値が足りなくとも、フローのステップが明確に分かれているので生成AIが処理を切り分けて推論してくれることであり、そしてプログラミング初心者自身がフローチャートを視覚で確認できるので「Aを処理したらBを処理する」と切り分けて考えやすいことです。
また、求めている結果ではなく誤った結果が生成されても、A - > B - > Cとフローを細分化していくことで生成AIの推論精度を高めていくことができるのも利点です。
より生成AIへ精度の高い推論をしてもらうために補足情報を付加するのも有用です。
digraph graphname {
A[
label = "Webページを表示する"
];
B[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機"
];
A -> B;
}
labelの記述内容もcommentの記述内容も生成AIが推論のための情報として利用するので誤った結果が生成されてもA - > B - > Cとフローを細分化しなくとも良い場合があります。
DOT言語を知るプログラミング有識者が「DOT言語の仕様を考えれば確かにそうだが、その発想はなかった」と言っていただけるであろうDOT言語コード例だとこういう記述方法もアリです。
digraph増田コード {
最初の処理[
label = "Webページを表示する"
];
次の処理[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機"
];
最初の処理 -> 次の処理;
}
ノードの名称へ自然言語を採用することにより、例えばゲームプログラミング時に「キャラクターがジャンプする」という読んだそのままな処理のためのノード、というか一般的に言うオブジェクトを作成することが可能で、後は->で繋げて処理をさせられます。
ちなみに別のノードを作成する際に「"キャラクターがジャンプする"から継承する」の様なことをcommentなどへ記述しておくと生成AIが推論して継承します。なんならcommentなどへ「キャラクター画像にimage.gifを使用」などと記述しておくとファイルの読み込みもします。
更にDOT言語にはカスタム要素という仕様が存在しており、DOT言語の仕様で定められた予約語以外も使用が可能です。
digraph増田コード {
最初の処理[
label = "Webページを表示する"
];
次の処理[
label = "Webページを表示したらHello, worldとテキストを表示する",
comment = "Webページが完全に読み込まれるまで待機",
font_style = "フォントを太字のボールド体、色を赤(#FF0000)とする"
];
最初の処理 -> 次の処理;
}
生成AIはカスタム要素の名称からも推論を発揮し、上記の場合であればフォントスタイルを指定していると推論をするので生成AIの推論精度を高める補足情報として機能します。
つまりこれはカスタム要素の名称として"Action"などの名称を採用すると"動作"として推論をし、"decision"ならば"条件分岐"ですし、"input"ならば"入力"ですし、"loop"ならば"繰り返し"ですし、"Type"ならば"種別"です。
より詳細に process[type="Action"] などのノードを作成してどんどん生成AIの推論精度を高めていくことが可能であり、そろそろ察してきているかと思いますが 処理[種別="動作"] と自然言語で記述しても機能します。
プログラミング有識者は更に「プログラム言語自体の予約語、例えばJavascriptを生成する事を前提にlengthを名称にすると配列を使おうとするのか?」と疑問に感じるでしょうがお察しの通りで生成AIは配列を使おうとするので、敢えて使いたいプログラム言語の機能や外部ライブラリなどがある場合は補足情報として機能する形で記述しておくと生成AIは推論へ利用します(まぁそこまで知識ある方なら該当のプログラム言語使ったほうが手っ取り早いと思いますが)。
以上をもって「生成AIを利用したプログラミング初級者向けの温故知新な提案」を終えたいと思います。
色々とツッコミどころには筆者自身が気付いていて。例えば「結局はDOT言語の仕様を覚えないといけないのでは?」とか「プログラミング初級者に任せると生成前のソースであるDOT言語コードがスパゲッティになりそうだよな」とか「面倒くせぇから普通にプログラミング覚えろや」とか理解してますし至極真っ当かつ反論の余地がないと思ってます。
今回の提案のプログラミング有識者向けの本質は「生成AIへ向いた中間言語の発掘」であり、「DOT言語ならそこそこ普及してるしプログラミング初級者でも扱えるんじゃね?」と業務中に発想したものを書き留め公開いたしました。
何かプログラミング有識者の皆さんからより良い発想があれば参考にしたいと考えていますのでよろしくお願いいたします。以上。
Permalink |記事への反応(36) | 19:36
Chrome系ブラウザには増田を快適に閲覧するためのコンパクトな増田 という古い拡張機能があったが、Chromeの更新に対応し切れておらず、既にChromeには新規インストールできなくなってしまっている。Edgeにはまだインストール可能だが、いずれ対応しなくなる可能性が高い。
そこで、「増田のトップページで、言及エントリ(返信・トラバ)を一覧から除外することで、新規エントリだけを一覧できる」という機能に絞ってコンパクトな増田を再現、ついでにいくつかのおまけ機能を付与したスタイルシート(CSS)を今年の4月に公開していたのだが、今回改めて英文スパム対策を追加したので公開する。
これを導入するにはStylus という拡張が必要で、少し気軽さには欠けるが、増田以外にも活用できるので、この機会にぜひ導入してみてほしい。拡張をインストールしたあとは、下記のコードをコピペして新規スタイルとして導入する方法もあるが、スタイルシートを公開できるuserstyles.world の増田CSSページ(※毎朝9:00直後はアクセスできない)から [Install]ボタンでインストールするほうが、自動更新にも対応するので便利かもしれない。
/*トップページで言及エントリを除外 *//*via:最近ファーストブクマカが静かhttps://anond.hatelabo.jp/20250326171302 */h1/*はてな匿名ダイアリー*/ + #intro/*名前を隠して楽しく日記。*/ + #body div.section:has(h3 > a/*■*/ + a:not(.keyword, .edit)/*anond:YYYYMMDDhhmmss*/){ display: none;}/* うっかりクリックしがちなキーワードリンクを無効に */a.keyword{ pointer-events: none;}/*執筆時のテキストエリアを広く */textarea#text-body{min-height: 50vh !important;}/*執筆時に特殊記号のヒント(疑似要素なので選択してコピペできないのがもどかしいけど) */p.post-submit >span.explain::after{margin-left: 1em;padding-left: 1em; content: "特殊記号: &[&] <[<] >[>]";background:url(/images/common/outsite-wh.gif) 0 3px no-repeat;}/*スパム対策部分は下記URLの [Install]ボタンで事前確認できます(随時更新中) *//*https://userstyles.world/style/23028/ */
なお、このCSSを適用すると、NGワードを含むこの増田自体も、増田トップページからは消えてしまう(この増田単体の個別ページなら閲覧できる)。
念のため、PC・スマホにCSSを適用する方法の解説にもリンクしておく。
PC: 【Stylus】ウェブサイトにCSSを適用できる拡張機能。自由にカスタマイズ! |ナポリタン寿司のPC日記
https://www.naporitansushi.com/stylus/
iPhone: MaKeoverアプリでiPhoneSafariのCSSをカスタマイズ!万博パビリオン予約結果一覧を見やすくする使い方
https://gintachan.com/makeover-app-css-change-safari-how-to/
(7/21追記) また、スパムが特に多い時は、1ページまるごとスパムということもあるので、PCならuAutoPagerize (Chrome) やweAutoPagerize (Firefox) などの拡張を使うと、自動でページが継ぎ足されて快適に読み進められる。ただし、継ぎ足ししまくるとメモリ不足などでブラウザが重くなることがあるので、そうなったら page:20 などのページ番号をクリックしてから続きを読もう。
また、スパム対策の簡易NGワードは、下記のスクリプトを使って抽出した「直近の増田の頻出キーワードリンク上位20件」から、誤判定しそうなline とuser を除いた18件を用いた。10件だと生き残る英文スパムがあったので20件にしたが、それでもわずかに洩れはある。しかし日本語による真っ当な(?)増田の直近の誤判定はなかった。はてなキーワードのリンクだけを対象にしているので、URLにはこれらのキーワードが入っていても大丈夫だ。ただし、スパムのトレンドが変われば話は変わってくるかもしれないし、過去や未来の増田の誤判定は当然あるだろう。気になる人は前掲のCSSを行単位で編集してほしい。
//AutoPagerizeでまとまった数のページを読み込ませた後に実行するとよい。(function(){constkeywords = []; //はてなキーワードの集計 document.querySelectorAll('a.keyword').forEach(a => { // 4文字未満は誤検出の可能性が高まるので除外 if(a.textContent.length < 4) return; letindex =keywords.findIndex(k => k.keyword === a.textContent); if(index >= 0)keywords[index].count += 1; elsekeywords.push({keyword: a.textContent, count: 1}); });keywords.sort((a, b) => a.count < b.count); //ランキング配列の出力console.log(keywords); //CSS埋め込み用に上位キーワードのみをURIエンコードして出力console.log(keywords.slice(0,20).map(k => encodeURIComponent(k.keyword)).join('\n'));})();
anond:20250326171302 ←元はこの増田がきっかけでした。
anond:20250701194328 ←キーワード判定に踏み切る後押しとなりました。
Permalink |記事への反応(12) | 23:56
一度投稿したうえで別タブを開いてプログラム的(fetch)に送信してその別タブが閉じられる仕組み。
// ==UserScript== // @namePGP未署名検出と別タブ自動編集 // @namespacehttp://tampermonkey.net/ // @version 1.0 // @descriptionPGP署名がない投稿を自動編集ページへ誘導 // @matchhttps://anond.hatelabo.jp/* // @grantGM_setValue // @grantGM_getValue // @grantGM.openInTab // ==/UserScript== (function () { 'use strict';constbody = document.getElementById('entry-page'); if (!body) return;consttitleText = document.title; if (!titleText.includes('dorawii')) return;constpgpRegex = /BEGIN.*PGP(?: SIGNEDMESSAGE| SIGNATURE)?/;const preElements = document.querySelectorAll('div.body pre'); let hasPgpSignature =false; for (const pre of preElements) { if (pgpRegex.test(pre.textContent)) { hasPgpSignature =true; break; } } if (hasPgpSignature) return;const editLink = document.querySelector('a.edit');const childTab =GM.openInTab(editLink.href, {active:false, insert:true,setParent:true }); })();
// ==UserScript== // @name編集ページ処理と自動送信・閉じ // @namespacehttp://tampermonkey.net/ // @version 1.0 // @description編集ページで署名処理と送信、タブ自動閉じ // @matchhttps://anond.hatelabo.jp/dorawii_31/edit?id=* // @grantGM_getValue // @grantGM_xmlhttpRequest // @grantGM_setClipboard // @grantGM_notification // @connectlocalhost // ==/UserScript== (async function () { 'use strict';const shouldRun = awaitGM_getValue('open-tab-for-edit', '0');consttextareaId = 'text-body';consttextarea = document.getElementById(textareaId); if (!textarea) return;const content =textarea.value;constpgpSignatureRegex = /-----BEGINPGP SIGNEDMESSAGE-----[\s\S]+?-----BEGINPGP SIGNATURE-----[\s\S]+?-----ENDPGP SIGNATURE-----/; if (pgpSignatureRegex.test(content)) {console.log('[PGPスクリプト]署名が検出されたためそのまま送信します'); return; }consthttpRequest = (url, data) => { return newPromise((resolve,reject) => {GM_xmlhttpRequest({ method: 'POST',url:url, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `value=${encodeURIComponent(data)}`,onload: function (response) { resolve(response.responseText); },onerror: function (error) {reject(error); } }); }); }; //textarea の値を取得 // 1.現在のページのURLからURLオブジェクトを作成const currentUrl = newURL(window.location.href); // 2.ベースとなる部分 (例: "https://anond.hatelabo.jp") を取得constorigin = currentUrl.origin; // 3. 'id'パラメータの値 (例: "20250610184705") を取得constidValue = currentUrl.searchParams.get('id'); // 4.ベース部分とIDを結合して、目的のURL文字列を生成 //idValueが取得できた場合のみ実行する let newUrl = null; if (idValue) { newUrl = `${origin}/${idValue}`; } // 5. 生成されたURLを変数に代入し、コンソールに出力して確認console.log(newUrl);constvalueToSend = newUrl;try {const signatureText = awaithttpRequest('http://localhost:12345/run-batch',valueToSend);console.log('バッチ応答:', signatureText); if (!signatureText.includes('BEGINPGP SIGNEDMESSAGE')) { alert('PGP署名がクリップボードに見つかりませんでした。'); return; }const newText = content.replace(/\s*$/, '') + '\n' + signatureText + '\n';textarea.value = newText;console.log('[PGPスクリプト]署名を貼り付けました。送信を再開します。');const form = document.forms.edit;const newForm = form.cloneNode(true); form.replaceWith(newForm); newForm.addEventListener('submit', async (e) => { e.preventDefault(); //HTML標準のsubmitをキャンセルconstbodyText =textarea?.value || ''; //reCAPTCHAトークンの取得constrecaptchaToken = await newPromise((resolve) => { grecaptcha.enterprise.ready(() => { grecaptcha.enterprise.execute('hoge', {action: 'EDIT' }) .then(resolve); }); }); // POSTするデータの構築const formData = new FormData(newForm); formData.set('body',bodyText); formData.set('recaptcha_token',recaptchaToken); formData.set('edit', '1');try {constresponse = await fetch(newForm.action, { method: 'POST',body: formData, credentials: 'same-origin' }); if (response.ok) {console.log('送信成功'); window.close(); } else {console.error('送信失敗',response.status); } }catch (err) {console.error('送信中にエラーが発生', err); } }); //プログラム的に送信トリガー newForm.dispatchEvent(new Event('submit', { bubbles:true })); }catch (e) {console.error('バッチ呼び出し失敗:', e); } })();
consthttp =require('http');const { exec } =require('child_process');const querystring =require('querystring');const server =http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/ping') { res.writeHead(200); res.end('pong'); } else if (req.method === 'POST' && req.url === '/run-batch') { letbody = ''; req.on('data', chunk => {body += chunk.toString(); }); req.on('end', () => {constparsed = querystring.parse(body);constvalue =parsed.value || 'default'; // 値を引数としてバッチに渡す exec(`C:\\Users\\hoge\\Desktop\\makesign.bat "${value}"`, { encoding: 'utf8' }, (err, stdout, stderr) => { if (err) { res.writeHead(500); res.end('Error executing batch: ' + stderr); } else { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(stdout.trim()); } }); }); } else { res.writeHead(404); res.end('Not found'); }});server.listen(12345, () => {console.log('Batch serverrunningathttp://localhost:12345/');});
@echo offsetlocal enabledelayedexpansion::署名するファイル名set "infile=%~1"set outfile=%TEMP%\pgp_output.asc:: 以前の出力があれば削除if exist "%outfile%" del "%outfile%":signloop::AutoHotkeyでパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える)start "" /b "C:\Users\hoge\Documents\AutoHotkey\autopass.ahk"::PGPクリア署名を作成echo %infile% | gpg --yes --clearsign --output "%outfile%"::署名が成功していればループを抜けるif exist "%outfile%" (goto postprocess) else ( timeout /t 1> nulgoto signloop):postprocesspowershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body =Get-Content '%outfile%' -Raw;Write-Output ($header + \"`r`n\" + $body + $footer)"powershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body =Get-Content 'signed.asc' -Raw;Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"endlocalexit /b
#Persistent#SingleInstance ignoreSetTitleMatchMode, 2WinWaitActive, pinentrySendInputpasswordSleep 100SendInput {Enter}ExitApp
動けばいいという考えで作っているので余分なコードも含んでいるかもしれない。
-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250613185036 -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEv1FQAKCRBwMdsubs4+SHHkAQDUOLgBcdji2T6MJ7h/vlMdFfGlWAzNdXijjE1gIuEPywEAiMNMZqhrMmtlc7UqRuggNJ/UTa5xTIcKp622+7jJQQg==Lgkl-----ENDPGP SIGNATURE-----
ようやく(ほぼ)すべてが自動化された。
あとはローカルサーバーの起動をスタートアップに設定する(方法をAIに聞いて指示に従う)だけの消化試合。
署名時要求してくるパスワードを自動入力するahkファイルはドキュメントのAutoHotkey配下に置いた。
バッチファイル(make.sign.bat)はデスクトップに置いた。
#Persistent#SingleInstance ignoreSetTitleMatchMode, 2WinWaitActive, pinentrySendInput お前のパスワードSleep 100SendInput {Enter}ExitApp
//run-batch-server.jsconsthttp =require('http');const { exec } =require('child_process');const server =http.createServer((req, res) => { if (req.url === '/ping') { res.writeHead(200); res.end('pong'); } else if (req.url === '/run-batch') { exec('C:\\Users\\you\\Desktop\\makesign.bat', (err) => { res.writeHead(200); res.end(err ? 'Error' : 'OK'); }) ; } else { res.writeHead(404); res.end('Not found'); }});server.listen(12345, () => {console.log('Batch serverrunningathttp://localhost:12345/');});
@echo offsetlocal enabledelayedexpansion::ミリ秒単位のUTC時刻を取得for /f %%a in ('powershell -nologo -command "[int64]::Parse((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmssfff'))"') doset timestamp=%%a::署名するファイル名set infile=%TEMP%\pgp_input.txtset outfile=%TEMP%\pgp_output.asc:: 以前の出力があれば削除if exist "%outfile%" del "%outfile%"::タイムスタンプを原文として保存echo %timestamp%> "%infile%":signloop::AutoHotkeyでパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える)start "" /b "C:\Users\infini\Documents\AutoHotkey\autopass.ahk"::PGPクリア署名を作成gpg --yes --clearsign --output "%outfile%" "%infile%"::署名が成功していればループを抜けるif exist "%outfile%" (echo [INFO]署名成功goto postprocess) else (echo [WARN]署名失敗、再試行します… timeout /t 1> nulgotosignloop):postprocess::PowerShellで余計な改行なしに |< をつけてクリップボードにコピーpowershell -nologo -command ^ "$header = '>|'; $footer = '|<'; $body =Get-Content '%outfile%' -Raw;Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"echo Done.signed.asc created and clipboard updated (no extra blankline).endlocalexit /b
// ==UserScript==// @namePGP署名自動付加スクリプト(GM_xmlhttpRequest版)// @namespacehttp://tampermonkey.net/// @version 1.0// @description投稿前にPGP署名を付けてから送信(fetch未使用)// @matchhttps://anond.hatelabo.jp/dorawii_31/edit*// @grant GM_xmlhttpRequest// @grant GM_setClipboard// @grant GM_notification// / @connectlocalhost// ==/UserScript==(function () { 'use strict';const submitId = 'submit-button';consttextareaId = 'text-body';const localServer = 'http://localhost:12345/run-batch';constpgpSignatureRegex = /-----BEGINPGPSIGNEDMESSAGE-----[\s\S]+?-----BEGINPGPSIGNATURE-----[\s\S]+?-----ENDPGPSIGNATURE-----/;consthttpRequest = (url) => { return newPromise((resolve,reject) => { GM_xmlhttpRequest({ method: 'GET',url:url, onload: function (response) { resolve(response.responseText); }, onerror: function (error) {reject(error); } }); }); };const interceptClick = () => {constbtn = document.getElementById(submitId); if (!btn ||btn.dataset.pgpIntercepted === 'true') return;btn.dataset.pgpIntercepted = 'true';btn.addEventListener('click', async function (e) {consttextarea = document.getElementById(textareaId); if (!textarea) return;const content =textarea.value; if (pgpSignatureRegex.test(content)) {console.log('[PGPスクリプト]署名が検出されたためそのまま送信します'); return; } e.preventDefault(); e.stopImmediatePropagation();console.log('[PGPスクリプト]署名が見つからないため処理を停止し、署名を取得します');try { awaithttpRequest(localServer); //バッチ実行constsignatureText = await navigator.clipboard.readText(); if (!signatureText.includes('BEGINPGPSIGNEDMESSAGE')) { alert('PGP署名がクリップボードに見つかりませんでした。'); return; }const newText = content.replace(/\s*$/, '') + '\n' +signatureText + '\n';textarea.value = newText;console.log('[PGPスクリプト]署名を貼り付けました。送信を再開します。');btn.click(); //イベント再発火 }catch (err) { alert('PGP署名の取得または貼り付けに失敗しました。\n' + err); } },true); }; window.addEventListener('load', () => {setTimeout(interceptClick, 1000); });})();
プロミスメソッドとか全然まだ理解してなくてそのなかに関数代入したその関数にオブジェクトのプロパティにresponseを?いやまあそのあたりのコードが示すデータの流れが全然理解できないような人間でもここまでできちゃった。
AIすごいなと思うよ。そして思うのは今後重要になってくるのは文法とか自体に詳しいことじゃなくて、そのプログラムの処理内容を指示できるシステムエンジニア的な言語化能力のほうじゃないかなと思った。
-----BEGINPGPSIGNEDMESSAGE-----Hash: SHA51220250609111559680 -----BEGINPGPSIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEbCbwAKCRBwMdsubs4+SLueAPwOv7PBk4voAe5qlcCEvs/PJhmKc5QAb/1R43JMQFuDZgD/UTPEKsL/PhK9jFGv2HDXK1dVjLNwvosgX9uYJh5xxwY==qiOE-----ENDPGPSIGNATURE-----
ちなみに
JSで0インデックスがヘダーのストリングのアレイのアレイからなるテーブルのカラムを別アレイのカラムインフォオブジェクトのディスプレイナンバーに合わせて並び替えて名前をnameからdisplayNameの値に入れ替えるコード
だと
想定される入力
['id', 'name', 'age'], // ヘッダー
['1', 'Alice', '30'],
['2', 'Bob', '25'],
];
const columnInfo = [
{name: 'name', displayName: '名前', displayNumber: 0 },
{name: 'age', displayName: '年齢', displayNumber: 1 },
{name: 'id', displayName: 'ID', displayNumber: 2 }
];
[
['Alice', '30', '1'],
['Bob', '25', '2']
]
function reorderAndRenameTable(table, columnInfo) {
constnameToIndex = header.reduce((acc,name,idx) => {
return acc;
}, {});
const sortedColumns = [...columnInfo].sort((a, b) => a.displayNumber - b.displayNumber);
// 新しいヘッダーを displayName に置き換え
const newHeader = sortedColumns.map(col => col.displayName);
// 各行の値も同じ順で並び替える
const newBody =body.map(row => {
return sortedColumns.map(col => row[nameToIndex[col.name]]);
});
return [newHeader, ...newBody];
}
ここまで数秒なのでまあこれくらいだと書くよりは早い
id を手動でコピーしてくるのが面倒だったから、削除ボタンが画面に出るようにした
押すと即削除
const rkm = "(トークン的なもの)"constuser = "(ユーザ名)"const delmasda = (id) => fetch(`https://anond.hatelabo.jp/${user}/edit`, { "headers": { "content-type": "application/x-www-form-urlencoded", }, "referrer": "https://anond.hatelabo.jp/", "referrerPolicy": "origin", "body": new URLSearchParams({ "rkm": rkm, "mode": "confirm", "id":id, "delete": "削除する" }).toString(), "method": "POST", })for (constsec of document.querySelectorAll(".section")) {constid =sec.querySelector("a").href.match(/92;/(92;d{14})/)[1]const delbtn = document.createElement("button") delbtn.textContent = "削除" delbtn.onclick = async () => { await delmasda(id)sec.remove() }sec.querySelector(".edit").after(delbtn)}
増田の削除をひとつひとつ詳細画面開いてするのが面倒だからまとめて消すスクリプトやで
const rkm = "(トークン的なもの)"constids = ["(削除する増田ID)"]constuser = "(ユーザー名)"for (constid ofids) fetch(`https://anond.hatelabo.jp/${user}/edit`, { "headers": { "content-type": "application/x-www-form-urlencoded", }, "referrer": "https://anond.hatelabo.jp/", "referrerPolicy": "origin", "body": new URLSearchParams({ "rkm": rkm, "mode": "confirm", "id":id, "delete": "削除する" }).toString(), "method": "POST", })
増田で 3 分以上投稿されない期間があるのか気になったから調べた
直近の 1 日だとこれだけあった
2025-03-22 00:14 -- 2025-03-22 00:182025-03-22 00:10 -- 2025-03-22 00:142025-03-21 07:56 -- 2025-03-21 08:002025-03-21 07:50 -- 2025-03-21 07:562025-03-21 07:44 -- 2025-03-21 07:482025-03-21 07:28 -- 2025-03-21 07:322025-03-21 06:58 -- 2025-03-21 07:032025-03-21 06:45 -- 2025-03-21 06:542025-03-21 06:32 -- 2025-03-21 06:372025-03-21 05:56 -- 2025-03-21 06:042025-03-21 05:51 -- 2025-03-21 05:562025-03-21 05:34 -- 2025-03-21 05:382025-03-21 05:30 -- 2025-03-21 05:342025-03-21 05:00 -- 2025-03-21 05:092025-03-21 04:56 -- 2025-03-21 05:002025-03-21 04:45 -- 2025-03-21 04:502025-03-21 04:09 -- 2025-03-21 04:132025-03-21 03:41 -- 2025-03-21 03:452025-03-21 03:29 -- 2025-03-21 03:392025-03-21 03:03 -- 2025-03-21 03:072025-03-21 02:56 -- 2025-03-21 03:022025-03-21 02:44 -- 2025-03-21 02:482025-03-21 02:33 -- 2025-03-21 02:372025-03-21 02:21 -- 2025-03-21 02:272025-03-21 02:14 -- 2025-03-21 02:19
秒はみてないから 00:01:01 - 00:03:59 はほぼ 3 分だけど 2 分扱いだし、 00:01:59 - 00:04:00 はほぼ 2 分だけど 3 分扱いになるくらいの誤差はある
日によって違うだろうし、曜日の影響も大きそうだから 1 ヶ月分くらい調査しようかと思ったけど、
増田の量が思いの外多すぎて 1 日分だけでも100 ページ以上取得しないといけなかった
件数だと 2500 以上
一応取得に使ったコードも載せとく
import{ setTimeout} from"node:timers/promises"import{Browser} from"happy-dom"const getTimestamps =asyncfunction* (){constbrowser =newBrowser()const page =browser.newPage()try{for (let num = 1; ; num++){await setTimeout(3000)await page.goto(`https://anond.hatelabo.jp/?page=${num}`)constdays = page.mainFrame.document.querySelectorAll(".day")for (const day ofdays){constdate = day.querySelector("h2 .date").textContent.trim()for (const footer of day.querySelectorAll(".sectionfooter")){consttime = footer.textContent.match(/\d{2}:\d{2}/)[0]yield`${date}${time}`}}}}finally{await page.close()awaitbrowser.close()}}constdiff = (a, b) =>{returnnewDate(b +":00") -newDate(a +":00")}let prev =nullforawait (constdatetime of getTimestamps()){if (prev &amp;&amp;diff(datetime, prev)>1000 * 60 * 3){console.log(datetime, prev)}prev =datetime}
基本は空いても 5 分程度であり、最大でも10 分となっている
以下chatgptによるremixjsの例
import { useEffect, useState } from "react";
import {json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
exportconst loader = async () => {
constresponse = await fetch("https://api.example.com/data");
const data = awaitresponse.json();
returnjson({ initialData: data });
};
export default functionIndex() {
const { initialData } = useLoaderData();
const [data, setData] = useState(initialData);
useEffect(() => {
const fetchData = async () => {
constresponse = await fetch("http://api.com/get");
const newData = awaitresponse.json();
setData(newData);
};
fetchData();
}, []);
if (initialData.value === null || data.value === null) {
return (
);
}
return (
<h1>Remix:SSR +クライアント処理</h1>
);
}
javascript:(()=>{const%20loc=document.querySelector('link[rel="canonical"]')||location;const%20urlStr=(loc.href.startsWith('about:reader'))?decodeURIComponent(loc.href.slice(17)):loc.href;const%20url=new%20URL(urlStr);let%20s=url.search;s=(s.startsWith('?page=')||s.startsWith('?P='))?"":s;const%20w=window.open('https://b.hatena.ne.jp/entry?url='+encodeURIComponent(url.origin+url.pathname+s));w.document.close();})();
https://gakuzzzz.github.io/slides/for_loop_to_higher_order_functions/#1
これはオブジェクト指向でも同じでみんな「バグを減らすため」にいろんなパラダイムに挑戦してる
それ以外のHowだとかWhatだとかオブジェクトで世界を表すだとかどうでもいい
for文よりmapとかfilterの方がなぜバグが少ないか、というと「余計な操作が入りにくいから」
特にletで宣言してるような書き換え可能な変数っていうのはバグの温床
例でも挙がってるようなProductのpriceの書き換えでもfor文にするとどこかにletな変数を置かないといけない
そんでletな変数っていうのはうっかり消してしまったりうっかり書き換えてしまっても気付くことができない
だからconstで固めて何かしらのリスト処理をするときはmapなりfilterなりを使ってconstに固め直す(か、そのまま使う)
逆に言うとそういう処理がないならfor文使っても全然構わない
この書き換え不可能な変数を作るっていうのはCだとかC++だとかJavaだとかの頃からずーっと一緒でとにかく固めておきたい変数はfinal宣言して書き換えさせない
そうしないと、めちゃくちゃ分かりづらいバグが混入して無意味に1週間とか過ごすことになる
Product.priceを直接書き換えるのはいいの?っていう疑問があるかもしれないが
「そこまでせんでも致命的にはならん」
っていう感じで型のチェックだけするのがTypeScript
とにかくプログラミングに関する規則でHowだとかWhatだとかそういうフワフワしたこと言い出したら要注意
たぶん大学とかでフワフワしたプログラミングだけしてて碌なプロダクト作ったことない
Permalink |記事への反応(14) | 22:55
地球は宇宙空間を動いているのだから、地球の進行方向と垂直方向では光の速さが変わるだろう。そう考えて実験してみたところ、どちらの速さも変わらなかった。つまり、どんな系でも光の速さは一定であるらしい。
これを式にするとこうなる。
光の速さをc, 時刻 t の間に光の進む距離を x として
x/t = c
式変形すると
ここで一旦休憩。座標系を回転させても'棒の長さは一定'という式を考えてみよう
x^2 + y^2 =const
かんたんのため z 方向は考えない
この時座系を回転させる式を行列で書くと
こうなる。(心の目で読んで欲しい)
という式を思い出すと
x'^2 + y'^2 = x^2 + y^2 =const
上の'棒の式'とは符号が逆だね。こんなときはsincos ではなくsinhcosh を使う。
cosθ = ((exp iθ) + (exp -iθ))/2
sinθ = ((exp iθ) - (exp -iθ))/2
coshθ = ((exp θ) + (exp -θ))/2
sinhθ = ((exp θ) - (exp -θ))/2
計算すると
cosh^2 -sinh^2 = 1 になるのがわかると思う。
光の速さが系を変換しても変わらないという式を行列で書くと
こうなる。 これがローレンツ変換。
棒の長さが一定、つまり空間回転は空間方向 (x,y,z)しか混ぜないけれど、
光のはやさが一定、つまりローレンツ変換は時間と空間 (t, x ) を混ぜているでしょ?
速さ v で進むロケットを考えてみよう。
v=x/t
だ。
一方、ロケットには美加子さんが乗っていてその携帯電話の表示では地球を発ってから T時間後である。
Tを計算してみよう。
先程のローレンツ変換の式に代入すると
ここで x =ct を使ったよ。最後にcosh で全体を纏めると
になる。
ここまで誤魔化していたけど、cosh はロケットの速さ v で決まるパラメータで
1/cosh = \sqrt{1-(v/c)^2}
なんだ。天下りで申し訳ないけど、増田では式も図も書けないので導出は勘弁して欲しい
とにかくまとめると
T = t \sqrt{1-(v/c)^2}
だね。ロケットの速度 v は光速度以下なので T < t になる。
地上で待つ昇くんが大学生になっても美加子さんが中学生のままなのはこんなワケだね
v が大きくなるほど時間の遅れは大きくなるよ
AIインフルエンサーたちはChatGPTが世に出る前は何を投稿していたのか?(1/1)の続き
このTwitterアカウントは、主にYouTube、SEO、競馬、アニメなどの話題について呟いており、自身の活動や興味関心について発信しています。
このアカウントは、日々の米国株や暗号通貨の市場動向、特にテクノロジー関連株やビットコインの価格変動についてツイートしています。
「ゼロコロナ政策再び。 $AAPL の生産に影響が懸念され大きく下落。更に経済混乱が想定され、株価はSP500全業種で
一方で年末商戦売上堅調な滑り出しで $AMZN
暗号資産レンディングBlockFiが経営破綻、暗号通貨も軒並み下落。
チャート上、なんとか踏み止まるか? 」
このアカウントは、AI、特にプロマネAIや量子AIに関する話題や、仕事効率化、プログラミング、そして最新の技術トレンドについて呟いています。
"プロマネAIの実証実験を開始しました!量子AI×NotionによるプロマネAIの実証実験を開始https://prtimes.jp/main/html/rd/p/000000003.000082094.html…via @PRTIMES_JP"
このアカウントは、主にOpenAIの最新言語モデル「text-davinci-003」の進化と、そのモデルを用いた英語学習ツールやサービスについて呟いています。特に、英語学習におけるAI活用とその重要性を強調しています。
"朝起きたら世界がまた変わっていた(笑)。@OpenAI が最新モデル「text-davinci-003」を発表。主な特徴は、①より明確で、説得力のある文章が書ける②より複雑な指示にも対応③より長い形式のコンテンツが生成可1月に"InstructGPT"が出た時も感動したけどそれを遥かに上回る進化、です。"
このアカウントは、主にAI、特に画像生成AIに関する話題を呟いています。特にStable Diffusionのバージョンアップや使い方について多くのツイートをしています。
ここにリストアップした人たち以外もみましたが、分類すると3種類のアカウントがいて
またIDを変更してログがヒットしない人は含まれていません(IDから特定できるけどそこまでやらなかった)
javascript:(function() {
consttext = Array.from(document.querySelectorAll('[data-testid="tweetText"]')).map(s => s.textContent.trim()).join('\n');
consttextarea = document.createElement('textarea');
document.body.appendChild(textarea);
try {
document.execCommand('copy');
console.log('結果がクリップボードにコピーされました!');
}catch (err) {
console.error('クリップボードへのコピーに失敗しました:', err);
}
document.body.removeChild(textarea);
})();
const axios =require('axios'); //HTTPリクエストを行うためのモジュール
constfs =require('fs'); //ファイル操作モジュール
constxml2js =require('xml2js'); //XMLをJSONに変換するためのモジュール
const chardet =require('chardet'); //文字エンコーディングを検出するためのモジュール
const iconv =require('iconv-lite'); //文字エンコーディングを変換するためのモジュール
constrssUrl = 'https://www.mlit.go.jp/important.rdf'; // 例としてRSSフィードのURLを指定
async function fetchAndSaveRSS() {
try {
//RSSを取得
constresponse = await axios.get(rssUrl, {responseType: 'arraybuffer' });
const detectedEncoding = chardet.detect(rssData);
console.log('検出された文字エンコーディング:', detectedEncoding);
//UTF-8に変換
const utf8Data = iconv.decode(rssData, detectedEncoding);
xml2js.parseString(utf8Data, (err, result) => {
if (err) {
console.error('XML解析エラー:', err);
return;
}
constjsonData =JSON.stringify(result, null, 2);
fs.writeFileSync('rss_feed.json',jsonData, 'utf8');
console.log('RSSフィードがファイルに保存されました。');
});
console.error('エラーが発生しました:',error);
}
}
// 実行
fetchAndSaveRSS();
特殊文字は、実体参照(& や <)ではうまくいきません。数値参照を使ってください。
| 出したい文字 | 数値文字参照 |
|---|---|
| & | & |
| < | < |
| > | > |
const dinner = (chicken,curry) => {
if (chicken &&curry) {
console.log('チキンかつカレー')
}
}
このように入力してください。
<pre>const dinner = (chicken,curry) => {<br> if (chicken &&curry) {<br>console.log('チキンかつカレー')<br> }<br>}</pre>
FANZAの検索結果から熟女を除外するブックマークレット がうまく動いたので、気を良くしてはてなブックマークのコメント欄をスター数順にソートするブックマークレットを作りました。
作った動機は、「注目コメントに入りきれなかったちょっといいコメント」をサクサク探したいから。結果として建設的コメント順位付けモデルを無効化していますが、あのアルゴリズムには特に不満は特にありません。
javascript: (async () => {
const wait =ms => newPromise(resolve => setTimeout(resolve,ms));
document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);
window.scrollTo(0, 0);
await wait(1000);
const p = document.querySelector('.js-bookmarks-recent');
let l = Array.from(p.querySelectorAll('.entry-comment-contents'));
const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n +Number(c.textContent) : n
};
l = l.filter(e => g(e) > 0);
l.sort((a, b) => g(b) - g(a));
p.replaceChildren(...l);
})();
ミニファイしたもの ※コードに一部誤りがありましたので訂正しました(2024-08-1611:47)
javascript:(async()=>{const wait=ms=>newPromise(resolve=>setTimeout(resolve,ms));document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();window.scrollTo(0,document.body.scrollHeight);await wait(1000);window.scrollTo(0,0);await wait(1000);const p=document.querySelector('.js-bookmarks-recent');let l=Array.from(p.querySelectorAll('.entry-comment-contents'));const g=e=>{let n=e.querySelectorAll('.hatena-star-star').length;const c=e.querySelector('.hatena-star-inner-count');return c?n+Number(c.textContent):n};l=l.filter(e=>g(e)>0);l.sort((a,b)=>g(b)-g(a));p.replaceChildren(...l)})()
FANZAの検索結果から熟女を除外するブックマークレット 参照
javascript:
ブックマークレットに必要な、URLの種類を示すスキーム名です。
(async () => {
// 処理
})();ページに元々ある変数たちとバッティングしないように、まず無名関数でラップします。処理の中で await を使いたいので async宣言しています。
const wait =ms => newPromise(resolve => setTimeout(resolve,ms));
document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);「新着コメント」タブをクリックし、ページの一番下までスクロールダウンしてから少し待つ動作です。新着コメントの後半部分(スクロールきっかけの遅延読み込みになっているところ)の読み込みをうながしています。
window.scrollTo(0, 0);
await wait(1000);
ページの先頭に戻ってまた少し待ちます。合計2秒の待ち時間は雰囲気で決めていますので、これでなければならない・これで過不足ないという値ではありません。単にコメントの読み込み完了を判定する処理を書くのがめんどうだっただけです。
const p = document.querySelector('.js-bookmarks-recent');
新着ブコメの親要素です。繰り返し呼び出すので名前をつけています。
let l = Array.from(p.querySelectorAll('.entry-comment-contents'));
const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n +Number(c.textContent) : n
};
コメントのはてなスター数をカウントして返す関数です。たくさんスターがついてる ★256★ みたいなやつの数字も足します。
l = l.filter(e => g(e) > 0);
ソートする前に、無スターのコメントを消去しています。してもしなくてもいいことですが。
l.sort((a, b) => g(b) - g(a));
残ったコメントをスター数で降順ソートします。.querySelectorAll() で収集した要素を配列に入れ直したのは、この .sort()メソッドを使いたいからです(.querySelectorAll() が返す配列風の NodeListオブジェクトは、配列と共通のメソッドもいくつかあるものの、大半は使えないのです)。
p.replaceChildren(...l);
親要素の内容を、並び替えの終わったコメントでそっくり入れ替えて、処理完了です。画面を見ると新着コメントの中身が「スターのついたコメントのみ・スターの多い順」に並んでいます。元に戻す方法はないので、原状回復にはリロードします。ソート状態を示すフラグを立てておいてスター数ソート⇔日付ソートをかわりばんこに行うようにすればできそうだなと思ったけど実装はしません。連打スターを省く処理を追加してUU数でソートできればもっと厳正なランキングになるなーと今思いつきましたがそれも実装はしません。