
はてなキーワード:console.logとは
async function f() {console.log(1); await newPromise(r => setTimeout(r, 1000));console.log(2);} f();console.log("done");
結果は、まず「1」「done」が出力され、1秒後に「2」が出力される。
流れを説明すると、f()が実行されると「console.log(1);」と「newPromise(r => setTimeout(r, 1000));」が実行される。
その時点でf()の戻り値として先ほどnewされた ……じゃなかった、f()を非同期実行中でそのうち続きが実行されますよというPromiseオブジェクトが返ってくる。
このPromiseオブジェクトは「resolveされたときにawait以降が実行される」というPromiseオブジェクトだ。
そして通常の処理の流れとして、その次の行の「console.log("done");」が実行される。
んで、1秒後にsetTimeoutで終わったことでキューに「r(イコール、resolve関数)」が登録される。
最後に、resolveが実行されたので、await以降……つまり「console.log(2);」が実行される。
どこか分からないとこある?
実行キューに入るのは非同期処理が終了した後だ。
なんか矛盾してない?
async function f() {
console.log(1);
newPromise(r => setTimeout(r, 1000));
console.log(2);
}
f();
console.log("done");
こう書いたらコンソールに出る順は1,2,doneだよ。1,2を出してる関数は非同期でありそれ以外にはこのコードに非同期関数は存在しないんだけど。
-----BEGINPGP SIGNEDMESSAGE-----
Hash: SHA512
https://anond.hatelabo.jp/20250908203539#
-----BEGINPGP SIGNATURE-----
iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaL6/iwAKCRBwMdsubs4+
SNVCAQDh/59YPp/11Ts/tp7JdxGIs6BqRv1PhkFmjUkBZH00owD/fN0PnyFGyJ8N
QGQlMNJvfsFGvNT5tbsEY1d/dhjCmQI=
=KsE1
-----ENDPGP SIGNATURE-----
ソースコードをいくら見てもそのソースコード作成者が想定してるエンジン側の実装なんて推測できんだろ。
ちなみにその「細かいこと」が気になった発端は初歩であるはずのコードの挙動の理解につまづいたからだけどな。
setTimeout(() => {console.log("A"); }, 1001); setTimeout(() => {console.log("B"); },1000);console.log("C"); 上のsetTimeout()の各第二引数の大小関係をどのように調整しても常に第二引数により小さい数が指定されたもののコールバック関数が先に実行されます。 これはどういうことですか?先にsetTimeoutと書かれたもののコールバック関数からキューに登録され、キューへの取り出し方はjsの場合fifoが採用されているので、上記のコードのような場合は、()=>console.log("A")の方が先にキューに登録されたものとして第二引数に無関係に()=>console.log("B")より先に実行されるのではないのですか?-----BEGINPGP SIGNEDMESSAGE-----Hash: SHA512https://anond.hatelabo.jp/20250908192456# -----BEGINPGP SIGNATURE-----iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaL6u+QAKCRBwMdsubs4+SAtwAP49XRX8yOJwd/XLSKjjP1TASfuVR29t/NIhuLSNb0vr2AD8CtGJTYMzavjSi9TuxJTV/DSYuwhuBLnKkd0lsJOldQI==wm4J-----ENDPGP SIGNATURE-----
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-----
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-----
増田で 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 分となっている
検索式を書きますので、それで見つかったファイルを全て選択して、削除などのアクションをしてください。
細かな調整は各自のお好みで変更してください。
◆1年以上前のファイルサイズの大きなメール(300kB以上)を検索(星を付けたメールは除く)。削除しましょう。
older_than:1y larger:300k -is:starred
◆プロモーションやソーシャルに分類された1か月以上前のメールを検索。削除しましょう
(category:promotions OR category:social) older_than:30d
◆受信トレイの180日以上前のメールを検索。アーカイブして、受信トレイのメール数を減らしましょう
label:inbox older_than:180d
◆プロモーションやソーシャルに入ったメールで2日経ったものを検索。既読にしましょう。
label:inbox (category:promotions OR category:social) older_than:2d
Google AppsScript(GAS)を使うとこれを毎日自動で行ってもらえます。
実行の左のアイコンで保存。関数をcleanUpGmailを選んで実行。初回は権限確認のメッセージが出るのでOKを押してください。
無事実行出来たら一番の難関はクリアです。これを毎日自動で実行してもらいましょう。一度に250通が処理されます。
また余裕があれば、左上の無題のプロジェクトになっているところの名前を「gmail自動処理」などに変更しても良いでしょう。
実行する関数:CleanUpGmailデプロイ:Headイベント:時間主導時間べース:時間ベース時間の間隔:6時間おき
これで1日に4回、合計1000通が自動処理されますので、たくさんメールが溜まっている方でも、1か月程度で全て処理されると思います。
function cleanUpGmail() { // メインの関数の開始ログconsole.log("=== cleanUpGmail start ==="); // 1) 2年以上前 &amp; 300KB以上 &amp; from:gmail.comではない &amp;スター付きではない → 削除console.log("古い大きなメールは削除"); processThreads("older_than:2y larger:300k -from:gmail.com -is:starred", "trash"); // 2)プロモーション orソーシャル &amp; 30日以上前 → 削除console.log("プロモーションとソーシャルは1か月で削除"); processThreads("(category:promotions OR category:social) older_than:30d", "trash"); // 3) 受信トレイ &amp; 180日以上前 →アーカイブconsole.log("受信トレイの180日以上前 →アーカイブ"); processThreads("label:inbox older_than:180d", "archive"); // 4) 受信トレイ &amp;プロモーション orソーシャル &amp; 2日以上前 →既読console.log("受信トレイでプロモーション orソーシャル かつ 2日以上前 →既読"); processThreads("label:inbox (category:promotions OR category:social) older_than:2d", "markRead");console.log("=== cleanUpGmail end ===");}function processThreads(query,action) { // 1回あたり250件だけ処理 var batchSize = 250; //最初の 250 件のみ取得 varthreads =GmailApp.search(query, 0, batchSize); var count =threads.length;Logger.log("検索クエリ: [" + query + "] | 取得スレッド数: " + count); //スレッドごとにアクションを実行threads.forEach(function(thread) {switch (action) {case "trash":thread.moveToTrash(); break;case "archive":thread.moveToArchive(); break;case "markRead":thread.markRead(); break; default:Logger.log("不明なアクション: " +action); } });Logger.log("処理したスレッド数: " + count);}
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();
anond:20240904032316についてたブクマカの初めて買ったCDのリリース年を調べた。
for (let a ofdocument.body.getElementsByClassName('entry-comment-textjs-bookmark-comment'))console.log(a.textContent)
ってやってテキスト集める。
Microsoft Copilotに「以下の文章に出てくる、音楽CDのタイトルとリリース年を一覧で表にしてください。」って指示。
途中で切れたので分割する。 続き→anond:20240905115337
特殊文字は、実体参照(& や <)ではうまくいきません。数値参照を使ってください。
| 出したい文字 | 数値文字参照 |
|---|---|
| & | & |
| < | < |
| > | > |
const dinner = (chicken,curry) => {
if (chicken &&curry) {
console.log('チキンかつカレー')
}
}
このように入力してください。
<pre>const dinner = (chicken,curry) => {<br> if (chicken &&curry) {<br>console.log('チキンかつカレー')<br> }<br>}</pre>
JavaScript でさあ
変数value が null でも undefined でもない事を確認するのに
if (value) {console.log('null でも undefined でもねーわ'); }
これほんとやめろって。
おかげでvalue に 0 とかが入ってる時に、このコンディションがfalse になるわけだ。
色んな会社さんのコード見てきたけど、このタイプのバグ本当に多い。
昨年は、世界的にも有名な会社さんのフレームワークがこれでバグってた。
でももう既にシステムの一部は本番稼働しててフレームワークはいじれない。
仕方ないのでvalue には一旦文字列の '0' を渡しておいて if (value) {~} の中の重要なロジックを動かして
(めっちゃ幸運な事に、数値 0 のかわりに文字列 '0' でも正しく動くような、型について緩いロジックだったから)
その後で改めてvalue に数値 0 を入れなおすという、きったないハックで誤魔化した事もある。
自分がお客様だったら怒るね。「いやいや、全部理想的なコードにしてちょうだいよ。お金払ってんだよ?」って。
<title>Document</title> の下に<scriptsrc="./script.js"></script> という行を追加する<body> と</body> の間にテスト文言を<h1>てすと</h1> とでも書いておくbodyの中に書いたテスト文言が左上に表示されているはずconsole.log("Hello, World!"); とタイプし、上書き保存する
Hello, World!
と出力されていれば成功。
これでJavaScript を実行する最小限の環境は整った。
好きなようにプログラムを書いてコンソールに出力したり画面に書き出したりしてみて。
「指示の通りにならない!」という時はどこでつまずいてるか書いて。対応策を助言できるかもしれない。
本当にあった話だけど、JavaScriptでstrっていう変数にテキストが入っていて
その変数に'apple'とか'banana'とかが入ってるかどうかを判別するっていうロジックを作るときに
const re =newRegExp("apple")if(re.test(str))console.log("match")
const re_apple =newRegExp("apple")const re_banana =newRegExp("banana")if ( re_apple.test(str) || re_banana.test(str) )console.log("match")
っていうコードを書く人がおるんよ
別にプログラミング初めて3ヶ月の初心者じゃ無くて20年以上やってるようなベテランだったり
なんならチーフプログラマーとして若手指導してるような人でもこういうレベルの人って割といるわけ
これに対して
「正規表現でORを書くべき」
「includeで十分」
綺麗にコードを書くっていうのは
「これってappleとかbanana以外に増えたりしないの?」
「fruitsかどうかを判別するならその変数を作った方が良くない?」
っていうのを考えて実装するのが綺麗にコードを書くっていうことで
ちなみに
「正規表現でORを書きましょう」
「動いてるからいいじゃない」
「綺麗に書いても性能上意味ない」
「言ってることが良く分からない。何が違うのか」
とか言ってくるし
https://b.hatena.ne.jp/site/anond.hatelabo.jp
で動くスクリプトでたとえば投稿後10分以内にブクマされページに乗ったら「1user」が「1userセルクマ 1とか5(何分後にブクマされたか)」になる。もしマイナスなら誤判定なので無視して。
時間を置いたセルクマには効かないし普通のファーストブクマカがどれぐらいの頻度で確認してるかしらないけど5分以内や1分以内もポロポロあるのでまあ目安に。
.forEach(div => {
('.entrylist-contents-title> a')
とかの
を
<>
に変えてね
他にも見落としあるかも
誤判定が減るから非公開ファーストブクマを判定できたらいいんだけどね。
// ==UserScript==// @name hatebu masuda selkmark// @namespacehttp://tampermonkey.net/// @version 0.1// @description特定時間以内にブクマされた増田を強調する// @authorYou// @matchhttps://b.hatena.ne.jp/site/anond.hatelabo.jp*// @grant none// ==/UserScript==(function() { 'use strict';const threshold = 60 *10 // 何秒以内かconst domain = 'https://anond.hatelabo.jp/'constdateTemplate = '202301020304' // 時分までurlの時刻表記constdateTest = newRegExp('\\d{' +dateTemplate.length + '}') document.querySelectorAll('div.entrylist-contents').forEach(div => {const masuda = div.querySelector('.entrylist-contents-title> a')constdateStr = masuda.href.substring(domain.length +dateTemplate.length, domain.length) if (!dateTest.test(dateStr)) { //キーワードとかconsole.log('not diary',dateStr) return } // newDateできるように変換 //https://amateur-engineer.com/javascript-date-yyyymmddhhmm/const year = parseInt(dateStr.substring(0, 4))const month = parseInt(dateStr.substring(4, 6))const day = parseInt(dateStr.substring(6, 8))const hour = parseInt(dateStr.substring(8,10))constmin = parseInt(dateStr.substring(10,12))constdate = newDate(year, month - 1, day, hour,min)const bukumaDate = newDate(div.querySelector('.entrylist-contents-date').textContent) // 2023/01/23 00:00const diffSec = (bukumaDate -date) /1000 //ms tosec if (diffSec> threshold) { return } //ブクマ数userconstuser = div.querySelector('span.entrylist-contents-users a').lastChilduser.textContent += 'セルクマ ' + (diffSec / 60) // 古い記事がマイナスになる でも2015年ぐらいの記事までかな?新着はセーフ臭い /* if(diffSec < 0) {user.textContent += ' 異常差分:' + diffSec } */ })})();
console.log(('b' + 'a' + + 'a' + 'a').toLowerCase())