ðš ãµã ã髪è²ã颿°1ã€ã§5ã¢ã«ãŠã³ãå šéšã«åºå®ãã話
æ10äžåã®å€§åŠçãæ°ã¥ãããæå120äžããã®èªåååºç€ã®çްéšããæãæ¢ãŸã£ãç¬éãå«ããŠæžããŠãããŸãã
ãªããã®ä»çµã¿ãå¹ãã®ã
SNSã§éç£åã®èšäºããžãã¹ããã£ãŠãã人ãæåã«ã€ãŸããã®ã¯ããèšäºãéç£ããããšãã§ã¯ãªããŠãéç£ããããšã®ãã©ã³ãã厩ããããšãã§ãã
ç§ã¯ä»ã5ã€ã®noteã¢ã«ãŠã³ãã䞊走ãããŠããŸããããŒãã¯ãã©ãã©ã§ãnote1ïŒ@bokuwalilyïŒã¯AI坿¥ã®å®é²ãnote2ïŒ@bokumolilyïŒã¯æžç±ã»ååã¬ãã¥ãŒãbuildïŒ@watashiwalilyïŒã¯SMBãžã®AIæé¡èª²éã®è©±ãaffiliateïŒ@watashimolilyïŒã¯ã¢ãã£ãªãšã€ãã®ãªã¢ã«ãfunnelïŒ@lilydesuïŒã¯åçåãã¡ãã«ã®èšé²ããžã£ã³ã«ãåããŠããã®ã¯ãã¢ã«ãŽãªãºã ã«åºããæèãå¢ããããã§ãã
ã§ã5ã¢ã«ãŠã³ããåããã€ãã©ã€ã³ã§åãããšãããåé¡ãå¿ ãèµ·ããŸãã**ãã©ã®ã¢ã«ãŠã³ãããåºãèšäºãªã®ãããµã ããèŠãã ãã§ã¯å€å¥ã§ããªããªãã**ãšãããã€ã§ãã
èšäºã®äžèº«ã¯LLMãæžãåããŠãããŸãããšããããµã ãã¯å¥ç³»çµ±ã§çæããŠããã®ã§ããã£ã©ã¯ã¿ãŒã®é«ªè²ãæ¯åéããbuildïŒéïŒã¬ãŒã³ã§åºããèšäºãªã®ã«ããã£ã©ã赀髪ã§åºãŠããæ¥ããããã¢ã«ãŠã³ãããšã«ã¢ã€ã³ã³ã®é«ªè²ã決ããŠããã®ã«ããµã ããããã«åŸã£ãŠããªãã£ãã
ããã¯èŠãç®ã ãã®åé¡ã§ã¯ãããŸããããã©ãã¯ãŒããµã ãã§ã¢ã«ãŠã³ããèªèããããšããåæã厩ãããšãããã®äººã®èšäºã ããèªãããšãã倿ãèµ·ããªããªããŸãããã©ã³ããç©ã¿äžãããªããå šéšãã©ãã©ã®ä»äººã«èŠããã
éãèšãã°ããµã ãã®ãã£ã©ãåã¢ã«ãŠã³ãã®ã¢ã€ã³ã³ãšåã髪è²ã«åºå®ããã ãã§ãèªè ã®èšæ¶ã«ããã®ã¢ã«ãŠã³ãã®é°å²æ°ããå·ã蟌ãŸããŠãããããã¯è€æ°ã¢ã«ãŠã³ãéçšã«ããããå°å³ã ãå¹ãæœçã§ãã
åœæãç§ã¯èªåæçš¿ã¹ã¯ãªãããæ¯æ¥åãããŠããŸããã1æ¥ã«5ã¬ãŒã³åèšã§10æ¬ä»¥äžã®èšäºãå ¬éãããããŒã¹ã§ããããµã ãã®é«ªè²ããã©ãã©ã«ãªã£ãŠãããã«æ°ã¥ãããšãããã§ã«44æ¬ã®å ¬éèšäºã誀ã£ã髪è²ã®ãµã ãã§åºãŠããŸãããæã§å·®ãæ¿ããŠãããäžž1æ¥æœ°ããéã§ãããããèªååã§è§£æ±ºããã®ããä»åæžã話ã§ãã
å šäœã®æµã
åé¡ã¯3å±€ã«åãããŠããŸããã
ãæ ¹æ¬åå ã
daily-codex-note.js ãã¬ãŒã³âãœãŒã¹å€æã§èª€ãããã³ã°
build â 'note' (赀髪ã«ãªã)
affiliate â 'note2' (é»é«ªã«ãªã)
â ä¿®æ£: source = lane.key ããã®ãŸãŸæž¡ã
ãgoing-forwardïŒæ°èŠèšäºïŒã
gen-codex-thumbnail.mjs ã® hairForSource(source) ã
HAIR_BY_SOURCE ããŒãã«ãåŒããŠæ£ãã髪è²ãè¿ã
â 䞊è¡ããŠå®è¡
ãã¬ãããã£ããïŒéå»44æ¬ïŒã
retrofit-hair-batch.mjs â note2ïŒé»é«ªïŒ27æ¬
retrofit-hair-codexlane.mjs â buildïŒéïŒ6æ¬ / affiliateïŒéïŒ6æ¬ / funnelïŒé»ç·ïŒ5æ¬
â API: GET /api/v2/creators/{urlname}/contents ã§å
¬éèšäºäžèЧååŸ
â ensureCodexThumbnail() ã§ãµã ãåçæ
â swap-eyecatch.js ã§Playwrightå·®ãæ¿ã
â state/retrofit-hair-*.json ã§åªçïŒéäžåéå¯ïŒ
hairForSource() ã®å®è£
ä¿®æ£ã®äžå¿ã¯ src/gen-codex-thumbnail.mjs ã«è¿œå ãã20è¡åŒ±ã®ã³ãŒãã§ãã
// 髪è²ã¯ãèšäºãåºãã¢ã«ãŠã³ãã®ã¢ã€ã³ã³ã®é«ªè²ãã«åããã(2026-07-20 ãŠãŒã¶ãŒæç€ºã»ä»¥åŸåºå®)ã
// note1=èµ€ / note2=é» / build=é / affiliate(AIãµãã¹ã¯)=é / funnel(lilydesu)=é»ç·ã
export const HAIR_BY_SOURCE = Object.freeze({
note: { en: 'red-haired', jp: '赀髪' },
note2: { en: 'black-haired', jp: 'é»é«ª' },
build: { en: 'blue-haired', jp: 'é髪' },
affiliate: { en: 'blonde', jp: 'é髪' },
funnel: { en: 'yellow-green-haired', jp: 'é»ç·é«ª' },
});
export function hairForSource(source) {
return HAIR_BY_SOURCE[source] || HAIR_BY_SOURCE.note;
}
Object.freeze() ããããŠããã®ã¯ãã¹ã¯ãªããã®ã©ããã§èª€ã£ãŠäžæžããããã®ãé²ãããã§ãããã©ãŒã«ããã¯ã¯ noteïŒèµ€é«ªïŒã«ããŠãããŸããæªç¥ã®ãœãŒã¹ãæ¥ããšãã«é»ã£ãŠå£ãããããããã©ã«ãã«èœãšãæ¹ããããã°ããããã
ãã® hairForSource() ã¯ãµã ãçæã®ããã³ããçµã¿ç«ãŠéšåã«å·®ã蟌ãŸããŸãã
function brandStyleForSource(source) {
return `${BRAND_STYLE_BASE}, fixed young ${hairForSource(source).en} anime boy as the hero`;
}
Codexãžæž¡ãããã³ããã®äžã«ãåºå®ãã£ã©ïŒãã髪ã®ã¢ãã¡ã®ç·ã®åããè±èªã§æ³šå ¥ãããç»åçæåšããã®æå®éãã®ãã£ã©ãæããLLMãCLIåŒæ°ãç¡èŠããŠå€ãæé ããäºæ ãéå»ã«èµ·ããŠããã®ã§ïŒãã³ãã«ã«å®åãçŒã蟌ãŸããä»¶ïŒã髪è²ãåæ§ã«ç°å¢ãã確å®ããå€ãçŽæ¥ããã³ãããžåãèŸŒãæ¹éã«ããŠããŸãã
ãã®é¢æ°ã¯ç¡æãµã ãïŒgen-codex-thumbnail.mjsïŒãšææã«ããŒïŒgen-paid-cover.mjsïŒã®äž¡ç³»çµ±ã«æ³šå
¥ãããŠããŸããäºç³»çµ±ã§å®çŸ©ãå
±æããŠããã®ã§ãå€ãããšãã¯1ãã¡ã€ã«ãçŽãã ãã§ãã
going-forwardãã°ã®æ ¹æ¬åå ãšä¿®æ£
颿°ãäœã£ãŠããåŒã³åºãåŽãééã£ãããŒãæž¡ããŠãããæå³ããããŸãããå®éãdaily-codex-note.js ããŸãã«ããããã£ãŠããŸããã
ã³ããã 7b78ca5 ã®diffããã®ãŸãŸèšŒæ ã§ãã
- const source = lane.key === 'build' ? 'note' : lane.key === 'affiliate' ? 'note2' : 'funnel';
+ // (æ§: buildânote/affiliateânote2 ã«åãããããŠããããããã ãšèµ€/é»é«ªã«ãªãèŠæãšé£ãéããã)
+ const source = lane.key;
ä¿®æ£åã®ã³ãŒã㯠build ã 'note'ïŒèµ€é«ªïŒã«ãaffiliate ã 'note2'ïŒé»é«ªïŒã«å€æããŠããŸãããããããåæå®è£
ã®ãšãã«ããœãŒã¹åããµã ã系統ã®ååã«åãããªããšãããªãããšãã誀解ããã£ãŠãæåã§å€æããŒãã«ãæžããŠããŸã£ãã®ã ãšæããŸããHAIR_BY_SOURCE ãäœã£ã段éã§ãã®å€æã¯äžèŠã«ãªã£ãââãšããããããããéªéã«ãªã£ãŠããã
ä¿®æ£ã¯ãã£ã1è¡ã§ããlane.key ããã®ãŸãŸ source ã«æž¡ããhairForSource('build') ãåŒã°ããã°é髪ãè¿ããåœããåã®è©±ãªã®ã«ãéäžã®éæ¥å€æãæ£ãããå£ããŠããã
ã¬ãããã£ãããããã®å åŽ
going-forwardã®ä¿®æ£ã¯ã³ããã1è¡ã§çµãããŸãããåé¡ã¯ããããã§ãããã§ã«å ¬éãããŠãã44æ¬ã®èšäºããä»ã誀ã£ã髪è²ã®ãµã ããã€ãããŸãŸçããŠããŸãã
æã§44æ¬ãçŽãæ°ã¯æåãããããŸããã§ããããµã ãã®å·®ãæ¿ãã¯Playwrightã§èªååæžã¿ã ã£ãã®ã§ãå
¬éèšäºäžèЧãååŸããŠãåçæããŠãå·®ãæ¿ãããšãã3ã¹ããããçµãã°ããããã ã5ã¬ãŒã³å
šéšã1æ¬ã®ã¹ã¯ãªããã«æŒã蟌ãã®ã¯é¿ããŸãããPlaywrightã®ãããã¡ã€ã«ãè¡çªããåé¡ïŒåŸè¿°ããŸãïŒããã£ãã®ã§ãnote2çšã® retrofit-hair-batch.mjs ãšãbuild/affiliate/funnelçšã® retrofit-hair-codexlane.mjs ãåããŠäœã£ãŠããŸãã
å ¬éèšäºäžèЧãã©ããã£ãŠåŒãã
noteã«ã¯éå
¬åŒãªããå®çšçãªAPIããããŸããGET /api/v2/creators/{urlname}/contents ã« kind=note&status=published ãã€ãããšããã®ã¢ã«ãŠã³ãã®å
¬éèšäºãããŒãžçªå·é ã§è¿ããŠãããŸããã¬ã¹ãã³ã¹ã® data.contents ã®åèŠçŽ ã« keyïŒèšäºåºæIDïŒãnameïŒã¹ã©ãã°ïŒãeyecatchïŒçŸè¡ãµã ãURLïŒãå
¥ã£ãŠããŸãã
ã¹ã¯ãªããã¯ãã®APIã page=1 ããé ã«å©ããis_last_page: true ãè¿ã£ãŠãããŸã§ããŒãžãããããŸããnote2ã ãš27æ¬ããããããŸããã
async function fetchPublishedNotes(urlname) {
const notes = [];
let page = 1;
while (true) {
const res = await fetch(
`https://note.com/api/v2/creators/${urlname}/contents?kind=note&status=published&page=${page}`
);
const json = await res.json();
notes.push(...json.data.contents);
if (json.data.is_last_page) break;
page++;
}
return notes;
}
ãã®APIã¯èªèšŒäžèŠã§å©ããŸããèªåã®ã¢ã«ãŠã³ãã®å ¬éèšäºãåæããã ããªã®ã§ãCookieãªãã§ååŸã§ããç¹ãå©ãããŸããã
ensureCodexThumbnail() ã§åçæãç©ã
èšäºäžèЧãæã«å
¥ã£ãããåèšäºã«ã€ã㊠ensureCodexThumbnail() ãåŒãã§ãµã ããåçæããŸãããã®é¢æ°ã¯ gen-codex-thumbnail.mjs ã«å®è£
ãããŠãããã®ã§ãéåžžã®èšäºæçš¿ãããŒã§ã䜿ã£ãŠãããã®ã§ãã
åäœã®å€§çã¯ã~/content/note-thumbnail-pipeline/queued/ ã«JSONãªã¯ãšã¹ããenqueueããâwatcher launchdãèµ·åâCodexã«çæãäŸé Œâdone/ ã«çµæãå±ãâvalidate_thumbnail_delivery.py ã§æ€èšŒâèšäºãã¹ãšååã® .png ã«ã³ããŒããšããæµãã§ãã
ã¬ãããã£ããçšã«å€ããã®ã¯ãèšäºã®ã¹ã©ãã°ïŒnameïŒãš sourceïŒnote2ãbuild çïŒãåŒæ°ã§æž¡ãã ãã§åãããã«åŒã³åºãåŽãèãããç¹ã ãã§ãã
await ensureCodexThumbnail({
title: article.name,
source: lane.key, // â hairForSource() ããããåç
§ãã
outputPath: tmpPngPath,
maxWait: 120_000,
});
source ã«æ£ãã lane.key ãæž¡ãããšã§ãhairForSource('build') ãé髪ãè¿ããçæããããµã ããæ£ãã髪è²ã«ãªããŸããgoing-forwardã®ä¿®æ£ãšæ ¹ãåãã§ãã
swap-eyecatch.js ã§Playwrightå·®ãæ¿ã
ãµã ããæå
ã«çæãããããæ¬¡ã¯å
¬éæžã¿èšäºã«è²Œãæ¿ããŸãããããæ
ãã®ã src/swap-eyecatch.js ã§ãã
æé ã¯ãèšäºã®ç·šéç»é¢ãéãâæ¢èšã¢ã€ã³ã³ã®deleteãã¿ã³ãã¯ãªãã¯âãã¡ã€ã«ã€ã³ãããã«æ°ããPNGãæž¡ãâãæŽæ°ããããã¿ã³ãæŒãããšãã4ã¹ãããã§ããåã¹ãããã§ã¹ã¯ãªãŒã³ã·ã§ãããä¿åããŠããã®ã§ãéäžã§æ¢ãŸã£ããšãã«ç®èŠã§ããŸãã
PlaywrightåŽã®ã³ãŒãã§æ°ãã€ããã®ã¯ããåé€ãã¿ã³ãåºãªãèšäºããžã®å¯ŸåŠã§ããæåããã¢ã€ãã£ãããèšå®ãããŠããªãèšäºã¯åé€ã¹ããããã¹ãããããŠããããªãã¢ããããŒããžé£ã³ãŸããããããããªããšãååšããªããã¿ã³ãæ¢ããŠã¿ã€ã ã¢ãŠãããŸãã
const deleteBtn = await page.$('button[aria-label="ã¢ã€ãã£ããç»åãåé€"]');
if (deleteBtn) {
await deleteBtn.click();
await page.waitForTimeout(500);
}
// åé€ã®æç¡ã«é¢ãããã¢ããããŒããž
åªçæ§ãstate JSONã§æ ä¿ãã
ã¬ãããã£ããã§äžçªæãã®ã¯ãéäžã§æ»ãã ãšããã©ããŸã§çµãã£ããããããªããªããããšã§ãã27æ¬ãé çªã«åŠçããŠããŠã15æ¬ç®ã§ãããã¯ãŒã¯ãåãããPlaywrightãã¯ã©ãã·ã¥ããããããšãæåããããçŽãã®ãæãïŒäºéå·®ãæ¿ãã«ãªãå¯èœæ§ãããïŒããæŸçœ®ããã®ãæãã
察çãšã㊠state/retrofit-hair-note2.json ã«é²æãæžããŠããæ¹åŒã«ããŸããã
{
"processed": [
{ "key": "nXXXXXX", "status": "done", "ts": "2026-07-20T04:12:00Z" },
{ "key": "nYYYYYY", "status": "done", "ts": "2026-07-20T04:14:30Z" }
]
}
ã¹ã¯ãªããã®å
é ã§ãã®JSONãèªã¿èŸŒã¿ãprocessed ã«å«ãŸãã key ã¯ã¹ãããããŸãããã©ãã§æ¢ãŸã£ãŠã node retrofit-hair-batch.mjs ãåå®è¡ããã°ç¶ãããåããç¶æ
ã§ãã
build/affiliate/funnelçšã® retrofit-hair-codexlane.mjs ãåæ§ã« state/retrofit-hair-codexlane.json ã䜿ã£ãŠããŸãã
ã¬ãŒã³å¥ã®å®çžŸ
æçµçãªå®çžŸã¯ä»¥äžã®éãã§ãã
| ã¬ãŒã³ | ã¢ã«ãŠã³ã | é«ªè² | å¯Ÿè±¡æ¬æ° | å®äº |
|---|---|---|---|---|
| note2 | @bokumolily | é» | 27 | 27/27 |
| build | @watashiwalily | é | 6 | 6/6 |
| affiliate | @watashimolily | é | 6 | 6/6 |
| funnel | @lilydesu | é»ç· | 5 | 5/5 |
åèš44æ¬ãnote1ïŒèµ€é«ªïŒã¯ããšããšæ£ããã£ãã®ã§å¯Ÿè±¡å€ã§ããnote1ã ãã¯èª€ãããã³ã°ã®è¢«å®³ãåããªãã£ããdaily-codex-note.js ã®å€æããŒãã«ã build ãš affiliate ã«å¯ŸããŠã®ã¿ééã£ãŠããã®ã§ãsource='note'ïŒnote1ïŒãš source='note2' ã¯ãã®ãŸãŸéã£ãŠããããã§ãã
ç§ãè©°ãŸã£ã話
å®è£ ã®å€§æ ã¯äžã«æžããéãã§ãããäžçºã§åããããã§ã¯ãããŸãããè©°ãŸã£ãç®æã3ã€ãçç¶ã»åå ã»çŽãæ¹ã®ã»ããã§æžããŠãããŸãã
è©°ãŸãâ --limit ãå
šç¶å¹ããªã
ãã¹ãå®è¡ã§ãããªã27æ¬ãåŠçããããªãã£ãã®ã§ã--limit 5 ãã€ããŠå°ããåãããšããŸãããã³ãã³ãã¯ãããªæãã§ãã
node retrofit-hair-batch.mjs --urlname bokumolily --limit 5
çµæã27æ¬å
šéšãåŠçãããŸããã--limit 5 ãå®å
šã«ç¡èŠãããŠããŸãã
åå ãæ¢ã£ãŠã¿ããšãåŒæ°ããŒãµãŒã«å žåçãªãã¹ããããŸããã
// ãã°ã®ãã£ãã³ãŒã
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--limit') {
opts.limit = parseInt(argv[i + 1]);
}
}
// 䜿ãåŽ
if (count >= opts.limit) break;
--limit ãåŒæ°åã®æ«å°Ÿã«çœ®ãããå Žåãargv[i + 1] 㯠undefined ã«ãªããŸããparseInt(undefined) 㯠NaN ãè¿ããŸãããã㊠count >= NaN 㯠垞㫠false ã§ããã©ãã ãã«ãŠã³ããå¢ããŠãäžéã«åŒã£ããããŸããã
ã«ãŒãã®çµäºæ¡ä»¶ããã£ãšæç«ããªããŸãŸå šèšäºãåŠçããŠããŸããŸããã
çŽãæ¹ã¯2ã¹ãããã§ãããŸã parseInt ã®çµæã NaN ã®ãšãã«ãšã©ãŒãæããããã«ããŠãåŒæ°ãã¹ãå³åº§ã«æ°ã¥ããç¶æ
ã«ããŸããæ¬¡ã« --limit ã --urlname ããåã«çœ®ãããåŒæ°ã®é åºã決ãæã¡ã«ããŸããã
opts.limit = parseInt(argv[i + 1]);
if (isNaN(opts.limit)) throw new Error(`--limit ã®æ¬¡ã«æ°å€ãå¿
èŠã§ã: ${argv[i + 1]}`);
æ°ãã€ããŠã»ããã®ã¯ããã®ç¶æ
ã§åå®è¡ãããšãã§ããstate JSON ã«ãdoneãã27æ¬æžãããŠããã®ã§ã--limit 5 ãã€ããŠåå®è¡ããŠã5æ¬ãã¹ããããããã ãã§æ°èŠåŠçã¯ãŒãã«ãªããŸããstate JSONãæ¶ããŠæåããããçŽãå¿
èŠããããŸãããåªçæ§ã¯å©ããä»çµã¿ã§ããããå
šéšããçŽãããããšãã«ã¯éªéã«ãªããŸããstate JSON㯠--reset ãã©ã°ã§åé€ã§ããããã«ããŠããã¹ãã§ããã
è©°ãŸãâ¡ Playwright ãããã¡ã€ã«ãè¡çªãã
retrofit-hair-codexlane.mjsïŒbuild/affiliate/funnel çšïŒã¯ãåã¬ãŒã³ã® Playwright ãããã¡ã€ã«ãéããŠãµã ããå·®ãæ¿ããŸãããããã¡ã€ã«ã¯ profiles/codex-buildã»profiles/codex-affiliateã»profiles/note3 ã®3ã€ã§ãã
åé¡ã¯ããããã®ãããã¡ã€ã«ã¯æ¯æ¥ã®æçš¿ãžã§ãã§ã䜿ãããŠãããšããããšã§ããdaily-codex-note.js ãæã®ç¹å®æå»ã«èµ·åããŠPlaywrightãç«ã¡äžããŸããChromiumã¯åäžã®ãããã¡ã€ã«ãã£ã¬ã¯ããªãè€æ°ããã»ã¹ãåæã«äœ¿ããŸãããã¬ãããã£ããã¹ã¯ãªããããžã§ãã®å®è¡äžã«èµ°ããšãChromiumãèµ·åããã§ããã«æ»ã«ãŸãã
çç¶ã¯ãããããããŠããããªãšã©ãŒãåºãŸãã
Error: Failed to launch chromium because another instance is already running.
æåã¯ãæçš¿ãžã§ããçµãã£ãŠããæåã§å®è¡ããã°ãããã§æžãŸããããšããŸãããã§ãæ·±å€ã«å®è¡ãããšãªããšããä»ãžã§ããåããŠãããããæ¯å確èªããã®ãé¢åã§ãã
察çãšã㊠ps aux ã§ããã»ã¹ãæ€æ»ããã³ãŒããå
¥ããŸããã
async function waitForProfileFree(profileDir, maxWaitMs = 30 * 60 * 1000) {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
const { stdout } = await execa('ps', ['aux']);
if (!stdout.includes(profileDir)) return; // 䜿çšäžããã»ã¹ãªã
console.log(`ãããã¡ã€ã«äœ¿çšäž: ${profileDir} â 60ç§åŸ
æ©`);
await sleep(60_000);
}
throw new Error(`${maxWaitMs / 60000}ååŸ
ã£ãŠããããã¡ã€ã«ã空ããŸããã§ãã`);
}
ãããã¡ã€ã«ãã¹ã ps aux ã®åºåã«å«ãŸããŠããªããã°ç©ºããšå€æããå«ãŸããŠããã°60ç§åŸ
ã£ãŠåãã§ãã¯ããŸããæå€§30ååŸ
ã€èšèšã§ããããã§ãæçš¿ãžã§ãã®å®è¡äžã«ãã£ããã¬ãããã£ãããèµ°ãããããã¹ãé²ããŸãã
å®éã«è©Šããããæ8æå°ã«èµ°ããããšããã¡ããã© daily-codex-note.js ãåããŠããŠ12ååŸ
æ©ããŠããåéããšããæåã確èªããŸãããstate/retrofit-hair-codexlane.json ã®é²æãæ®ã£ãŠããã®ã§ãåŸ
æ©åŸã®åéãå®å
šã«åªçã§ãã
è©°ãŸã⢠ãµã ãã®ã¢ãã¡ãã£ã©ã«ðŸãè±è ã«ãªã
ã¬ãããã£ããã§ãµã ããåçæããŠãããšããäžéšã®èšäºã§ãµã ãã®æåãâ¡ïŒè±è ïŒã«ãªãåé¡ãèµ·ããŸãããå ·äœçã«ã¯ funnel ã¬ãŒã³ã®èšäºã¿ã€ãã«ã«çµµæåãå«ãŸããŠãããã®ã§ãã
ããšãã°ã¿ã€ãã«ããðŸ AIãæžããã¢ãã£ãªèšäºã§å®éã«å£²ãã話ãã®ãšãããµã ãäžã®æååæã¹ã¯ãªãã overlay_note_text.py ããã©ã®ãè§ãŽW9ã§æç»ããããšããŠðŸãè±è
ã«ãªããŸãããã©ã®ãã¯SFã·ã³ãã«ãçµµæåãã©ã³ããå
å
ããŠããªãã®ã§ãU+1F43EïŒèçïŒã衚瀺ã§ããŸããã
çç¶ã¯éãã§ããã¹ã¯ãªããã¯æ£åžžçµäºããPNGèªäœã¯çæãããŸãããã ããã®PNGãç®ã§èŠããšæåã®å é ã«â¡ãããããµã ããšããŠå ¬éãããŠããæ°ã¥ããã¿ãŒã³ã§ãã
ä¿®æ£ã¯ sanitize_text() 颿°ã«çµµæåé€å»ã远å ããŸããã
import re
EMOJI_PATTERN = re.compile(
"["
"\U0001F300-\U0001F9FF" # é¡ã»ç©ã»åç©ã»é£ã¹ç©ã»å Žæ
"\U00002600-\U000027BF" # ãã®ä»èšå·ïŒâðçïŒ
"\U000023F0" # ç®èŠãŸãæèšïŒU+27BFã®é€å€æŒã察çïŒ
"]+",
flags=re.UNICODE
)
def sanitize_text(text: str) -> str:
return EMOJI_PATTERN.sub('', text).strip()
--title ãš --kicker ã«æž¡ãåã« sanitize_text() ãéãããšã§ãçµµæåãæ¶ããç¶æ
ã§æç»ãããŸãã
ããã¯æ ¹æ¬è§£æ±ºã§ã¯ãããŸããããçµµæå察å¿ãã©ã³ãïŒNoto Color EmojiçïŒãåæã«çµã¿èŸŒãããæ¬æ¥ã®çŽãæ¹ã§ãããåæã¹ã¯ãªããã®ãã©ã³ãã¹ã¿ãã¯ãå€ãããšãã¹ãŠã®ãµã ãã®å質ã«åœ±é¿ãããããåœé¢ã¯é€å»ã§éçšããããšã«ããŸããã
ãªãããã®ä¿®æ£ãå¹ããŠãããã®ç¢ºèªæ¹æ³ãšããŠãåãçµµæåããã¿ã€ãã«ã§ä¿®æ£ååŸã® PNG ãçæã㊠SHA-256 ãåããé€å»ããå Žåã®ããã·ã¥ãäžèŽããŠãããã確èªããŸãããçµµæåã®æç¡ã§èŠãç®ã¯å€ãããŸãããé€å»åŸã®çµæã決å®è«çã«åãã«ãªããã®ãã§ãã¯ã§ããä¿®æ£ååŸã§é€å»æžã¿ããŒãžã§ã³ã®ããã·ã¥ãäžèŽããã®ã§ååž°ãªãã確èªã§ããŸããã
å®è£
ãæ¯ãè¿ããšãä»åã®äœæ¥ã§äžçªå¯åºŠãé«ãã£ãã®ã¯ã¬ãããã£ããã§ã¯ãªããæ ¹æ¬åå ã1è¡ã®ä¿®æ£ã§å°ããããšããã®1è¡ãå¹ããŠãããã44æ¬ã®å®èšäºã§èšŒæããéšåã§ãããhairForSource() ã®å®è£
ã¯20è¡ãsource = lane.key ã®ä¿®æ£ã¯1è¡ãã§ãã¬ãããã£ãããå«ãå
šäœã®äœæ¥éã¯300è¡è¶
ã®ã¹ã¯ãªãã2æ¬ã§ãã
å°ãããã°ãç©ã¿äžãã£ã44æ¬ã®è² åµãè¿æžããã³ã¹ãã¯ããã°ãæžããç¬éã®ã³ã¹ãããäœåã倧ãããããã¯ä»åã®è©±ã«éãããèªååãã€ãã©ã€ã³ãè€æ°äžŠèµ°ããããšãã«åžžã«æèããŠããã¹ãããšã ãšæã£ãŠããŸãã
ã€ãŸãããã€ã³ã
ååã»äžæ®µã§è©³è¿°ãã3ç¹ïŒ--limit ã®NaNåé¡ãPlaywrightãããã¡ã€ã«è¡çªãçµµæåè±è
ïŒã«å ããå®è£
å
šäœãæ¯ãè¿ã£ãŠèŠããŠããèœãšã穎ããŸãšããŸãããå®è£
äžã«è©°ãŸã£ãç¬éãã ãã§ãªããããšããæ°ã¥ããèšèšãã¹ããå«ããŠããŸãã
â going-forward ä¿®æ£ â åäœç¢ºèª â retrofit ã®ãé åºããå®ããªããšäºéä¿®æ£ã«ãªã
source = lane.key ã®1è¡ä¿®æ£ïŒcommit 7b78ca5ïŒãããŠããã¬ãããã£ããã¹ã¯ãªãããèµ°ãããªããšãretrofit ã§æ£ãã髪è²ã«çŽããéå»44æ¬ã«å¯ŸããŠãç¿æ¥ã® daily-codex-note.js ãå€ããããã³ã°ïŒbuild â 'note'ïŒã§æ°ãµã ããçæããŠäžæžãããŸããã€ãŸããçŽããã¯ããªã®ã«ãŸãæ»ã£ãŠããããšããç¶æ
ã«ãªããŸããä»å㯠going-forward ä¿®æ£åŸã« build ã¬ãŒã³ã§æ°èŠèšäºã1æ¬æçš¿ããŠé髪ãåºãããšãç®èŠç¢ºèªããŠããããããèµ°ãããŸãããããããæç€ºçã«ãæé ããšããŠå®çŸ©ããŠãããªãã£ãããã確èªäœæ¥ãå±äººçãªèšæ¶ã«äŸåããŠããŸãããããã°ä¿®æ£ãæ°èŠããŒã¿ã«æ£ããé©çšãããŠããããšã確èªããŠãããéå»ããŒã¿ãä¿®æ£ããããšããé åºã¯ã«ãŒã«ãšããŠææžåãã¹ãã§ããã
â¡ hairForSource() ã®ãã©ãŒã«ããã¯ã赀髪ã ãšããã°ã®çãæ®ãããšåºå¥ã§ããªããªã
ãã©ãŒã«ããã¯å
ã noteïŒèµ€é«ªïŒã«ããã®ã¯ãæªç¥ã®ãœãŒã¹ãæ¥ããšãé»ã£ãŠå£ãããããããã©ã«ãã«èœã¡ãæ¹ããããã°ããããããšãã倿ã§ãããã ãããããã®ãã°ããbuild ã®èšäºã赀髪ã§åºãŠããããšãããã®ã ã£ãã®ã§ãgoing-forward ä¿®æ£åŸã« build ã¬ãŒã³ã®æ°èŠèšäºã«èµ€é«ªã®ãµã ããåºãŠãããšãããããããã©ãŒã«ããã¯çºç«ïŒæªç¥ã® source ãæ¥ãããªã®ããä¿®æ£ããŸã åæ ãããŠããªãããªã®ããç®èŠã ãã§å€æã§ããŸããã§ãããäºåŸçã«èãããšãéçºã»ãã¹ãç°å¢ã ã process.env.STRICT_HAIR_SOURCE ãèšå®ãããŠããããã©ãŒã«ããã¯æã«äŸå€ãæããåãæ¿ããå
¥ããŠãããšãä¿®æ£ãæ£ããæ©èœããŠãããã®æ€èšŒãæ Œæ®µã«æ¥œã«ãªããŸããæ¬çªã¯ããã©ã«ããã©ãŒã«ããã¯ãç¶æããªããããã¹ãæã ã strict ã¢ãŒãã§åãããšãã2段æ§ããçæ³ã§ãã
⢠retrofit å®äºåŸã®ãæ¬åœã«çŽã£ãŠãããããèªåæ€èšŒããææ®µããªãã£ã
swap-eyecatch.js ã¯ãPlaywright ãæŽæ°ãããã¿ã³ãã¯ãªãã¯ã§ããããšãã確èªããããŠããŸããããã¿ã³ãæŒããããšãšãã¢ããããŒããã PNG ãæ£ãã髪è²ã«ãªã£ãŠããããšã¯å¥ã®äž»åŒµã§ããå®éã«ã¯ã¢ããããŒãå
ã® URL ã fetch ããŠãã¯ã»ã«å€ãæ€æ»ããããå°ãªããšããµã ãã® URL ãå€ãã£ãŠããããšã確èªããã¢ãµãŒã·ã§ã³ãå¿
èŠã§ãããä»åã¯44æ¬ãæäœæ¥ã§ç®èŠç¢ºèªããŸãããã100æ¬ã»300æ¬ã®ã¹ã±ãŒã«ã«ãªããšç®èŠã¯çŸå®çã§ã¯ãããŸããããã¹ã¯ãªããã exit 0 ã§çµãã£ãããšãæåŸ
ããææç©ãå®åšãããã¯å¥ã®åœé¡ãšããŠæ±ããã¬ãããã£ããç³»ã®ã¹ã¯ãªããã«ã¯å¿
ãäºåŸæ€èšŒã¹ããããçµã¿èŸŒãå¿
èŠããããŸãã
⣠Object.freeze() ãå¿ãããšå¥ã¹ã¯ãªããããã®èª€äžæžããç¡é³ã§éã
export const HAIR_BY_SOURCE = Object.freeze({
note: { en: 'red-haired', jp: '赀髪' },
build: { en: 'blue-haired', jp: 'é髪' },
// ...
});
Object.freeze() ãªãã§ãšã¯ã¹ããŒãããå Žåãã€ã³ããŒãããå¥ãã¡ã€ã«ã§ HAIR_BY_SOURCE.build = { en: 'red-haired', jp: '赀髪' } ã®ãããªä»£å
¥ãå®è¡æã«äœã®ãšã©ãŒãåºããã«éããŸããæ¬çªã§å®éã«èµ·ããããã§ã¯ãããŸãããã5ã¬ãŒã³ä»¥äžã®ã¹ã¯ãªããã1ã€ã®ããŒãã«ãåç
§ããã³ãŒãããŒã¹ã§ã¯ãfreeze ãéçãªé²æ³¢å €ãšããŠæ©èœããŸããç¹ã« Codex ã«å®è£
ãå§è²ããŠãããšããCodex ãã䜿ããããã®ããã«ãããŒãã«ã®ãšã³ããªã远å ãããæžãæãããããã³ãŒããçæããå¯èœæ§ãããã®ã§ãfreeze ã§ç©ççã«é²ãæå³ããããŸãã
†ããŒãžããŒã·ã§ã³ API ã§ is_last_page ã ããçµäºæ¡ä»¶ã«ãããšå±ãªã
fetchPublishedNotes() ã®ã«ãŒãã¯ã¬ã¹ãã³ã¹ã® json.data.is_last_page ã true ã«ãªããŸã§ããŒãžãã€ã³ã¯ãªã¡ã³ãããŸããnote API ããããã¯ãŒã¯é害ãäžæçãª5xx ã§ãšã©ãŒã¬ã¹ãã³ã¹ãè¿ããå Žåãjson.data ãååšããªãç¶æ
ã§ is_last_page ãåç
§ãããš undefined ã«ãªããŸããundefined 㯠true ãšçãããªãã®ã§ã«ãŒãã¯æ¢ãŸããŸãããä»å㯠note ã® API ãå®å®ããŠããã®ã§å®å®³ã¯ãããŸããã§ããããif (!json?.data?.contents?.length) break ãšããå®å
šåŒã远å ããããšã§ã空ã®ããŒãžãè¿ã£ãŠããå Žåãæ£åžžã«çµäºã§ããŸãã
⥠sanitize_text() ã retrofit ãã¹ã«ãå¿
èŠããæåããèšèšã«å
¥ããŠããªãã£ã
çµµæåè±è
ã®å¯Ÿçãšã㊠sanitize_text() ã overlay_note_text.py ã®æç»å
¥å£ã«çœ®ããããšã§ãgoing-forward ã§ã retrofit çµç±ã®åŒã³åºãã§ãèªåã§çµµæåãé€å»ãããŸãããã ãããã®é¢æ°ã¯æç»ãã€ãã©ã€ã³ã®å
¥å£ã«çœ®ããŠããã®ã§åŒã³åºãå
ã§ sanitize ããªããŠããããšããããã¥ã¡ã³ããååšããªãã£ããããretrofit ã¹ã¯ãªãããæžããšãã«ããã¡ãåŽã§ã sanitize ãå¿
èŠããã調ã¹ãæéãçºçããŸããããã©ãã«äœã®è²¬ä»»ãæãããŠããããã®èšèšæå³ãäžè¡ã§ããã®ã§ã³ãŒãå
ã«æžãæ®ãããŸã㯠README ã«èšé²ããŠããç¿æ
£ãå¿
èŠã§ããã
⊠state JSON ã« --reset ãã©ã°ããªããšãå
šéšããçŽãããã§ããªã
state/retrofit-hair-note2.json ã¯åŠçæžã¿èšäºã® key ãèšé²ããåå®è¡æã«ã¹ãããããåªçèšèšã§ããããèªäœã¯æ£ããã®ã§ããã--limit NaN ãã°ã§å
šä»¶åŠçãããŠããŸã£ãããšãæåã®5æ¬ã ãæ¹ããŠåŠçãããããšããèŠæ±ãçãŸãããšããstate JSON ãæåã§åé€ãã以å€ã®æ¹æ³ããããŸããã§ãããstate JSON ãåé€ãããšããããšã¯ãã©ããŸã§çµãã£ãããã®èšé²ãæ¶ããããšã«ãªããŸãã--reset ãã©ã°ã§ state JSON ã ãã¯ãªã¢ããŠãããããŒã«å
¥ãéžæè¢ãšã--only-keys key1,key2 ã§ç¹å®èšäºã ãååŠçãããªãã·ã§ã³ãããã°ãåŸããã®èª¿æŽã倧å¹
ã«æ¥œã«ãªããŸããã
â§ 5ã¬ãŒã³ã®ãããã¡ã€ã«ãã¹ãããŒãã³ãŒããããšè¿œå ã¬ãŒã³æã«å šã¹ã¯ãªãããçŽãããšã«ãªã
comment-lanes.json ã§ã¬ãŒã³ãšãããã¡ã€ã«ã®å¯Ÿå¿ãéçŽããåãåã¹ã¯ãªãããåå¥ã«ãããã¡ã€ã«ãã¹ãæã£ãŠããææããããŸãããfunnel ã¬ãŒã³ãæ°èŠè¿œå ãããšãã«ãããã¡ã€ã«ã®æžãæããå¿
èŠãªã¹ã¯ãªããã6æ¬ä»¥äžãããæžãæãæŒãããããã¡ã€ã«è¡çªã®åå ã®äžã€ã«ãªããŸãããèšå®ã®1ç¹éäžç®¡çã¯ã倿޿Œãããé²ãæ§é çãªè§£ã§ããããåŸä»ãã§å°å
¥ããããæåãããã£ãæ¹ãã³ã¹ããäœãã
âš ã¬ãããã£ããäžã« daily ãžã§ããèµ·åããã¿ã€ãã³ã°ã¯äºæž¬ã§ããªã
retrofit-hair-codexlane.mjs ã®å®è¡äžã« daily-codex-note.js ãæã®ã¹ãããïŒbuild 7:15ãaffiliate 8:15ãfunnel 10:40ïŒã«èµ·åãããšãããã¡ã€ã«ãè¡çªããŠå³ã¯ã©ãã·ã¥ããŸããwaitForProfileFree() ãå
¥ã㊠ps aux ã§ãããã¡ã€ã«ã®ãã¹ã60ç§ããšã«ãã§ãã¯ããèšèšã«ããŸããããããã¯ãããŸã§ã¬ãŒã¹ç¶æ
ããæ€ç¥ããŠåŸ
ã€ãã¢ãããŒãã§ãããã確å®ãªã®ã¯ãdaily ãžã§ãã®å®è¡ã¹ãããå€ã®æé垯ã«ã®ã¿ retrofit ãèµ°ããããã launchd ã®ã¹ã±ãžã¥ãŒã«ã«å¶çŽãå
¥ããããã°ããŒãã«ãªããã¯ãã¡ã€ã«ã䜿ã£ãŠã誰ãã Playwright ã䜿ã£ãŠãããç¶æ
ã管çããããšã§ãã
â© ä¿®æ£ã going-forward ã«å¹ããŠãããã確èªããåã«ãå®äºããšå€æãããã«ãªã£ã
source = lane.key ã®ä¿®æ£ã¯diffã1è¡ã§ããã³ãŒãã¬ãã¥ãŒã ãèŠããšãããã§åãã¯ãããšããæèŠã匷ããªããŸãããã ããåãã¯ãããšãå®éã«åãããã¯å¥åœé¡ã§ãä»åã¯ä¿®æ£åŸã«æ°èŠèšäºã1æ¬å®æçš¿ããŠé髪ã®ãµã ããåºãããšã確èªããŠããretrofitã«ç§»ããŸãããèªååãã€ãã©ã€ã³ã®ä¿®æ£ã¯ããã³ãŒããå€ãã£ãããšãã確èªãããæ£ããåºåãå®éã«çæãããããšãã確èªã§å®äºãšå€æããã¹ãã§ããã¹ããŒãžã³ã°ç°å¢ããªãå Žåã¯ãæ¬çªã§1æ¬ã ãæçš¿ããŠç¢ºèªããšããæé ããã§ãã¯ãªã¹ãã«å
¥ããŠããã
⪠going-forward ãã°ã«æ°ã¥ããã®ã44æ¬ããŸã£ãŠããã ã£ã
ãµã ãã®é«ªè²ãã°ã«æ°ã¥ããã®ã¯ãèšäºã44æ¬å ¬éãããããšã§ããã1æ¥10æ¬ããŒã¹ã§èªåæçš¿ããŠããã®ã§ãçŽ4ã5æ¥éã®ãã©ã³ã¯ã§ãããµã ããæ¯æ¥ç®èŠãã§ãã¯ããéçšãããŒããªãã£ããããããªããè²ããã©ãã©ã ãªããšããå°è±¡ãèšæ¶ã«ç©ã¿äžãããŸã§æ€ç¥ã§ããŸããã§ãããèªååãã€ãã©ã€ã³ã«ã¯ãåºåå質ã®ãµã³ããªã³ã°ç¢ºèªããæ¥æ¬¡ãŸãã¯é±æ¬¡ã§å®æœããéçšã«ãŒã«ãå¿ èŠã§ããä»åã§ããã°ãåã¬ãŒã³ã®ææ°èšäºã®ãµã ããæ£ãã髪è²ããæ¯æç¢ºèªãã1åã®ãã§ãã¯ãã§44æ¬ã4ã5æ¬ã«æããããŠããŸããã
ãã¹ããã©ã¯ãã£ã¹
ä»åã®å®è£ ããæœåºãããè€æ°ã¢ã«ãŠã³ãèªååãã€ãã©ã€ã³ã§ç¹°ãè¿ã䜿ããèšèšååã§ããããªããããããããŸã§æžããªããšæ¬¡ã«è¿·ã£ããšãã«äœ¿ããªãã®ã§ãååãšçç±ãã»ããã§èšé²ããŸãã
â ãœãŒã¹èå¥åã¯éäžã§å€æããªã
lane.key ã source ã«å€æãã鿥ããŒãã«ã1ç®æã§ãæžãããããã®ããŒãã«ããçå®ã®åäžæºæ³ããæã€ããšã«ãªããŸããä»åã®ãã°ã¯ build â 'note'ãaffiliate â 'note2' ãšãã倿ããŒãã«ã HAIR_BY_SOURCE ã®è¿œå åŸãçãæ®ã£ãŠããããšã§èµ·ããŸãããsource = lane.key ã®ããã«ãèå¥åã¯ãã®ãŸãŸæž¡ãããååã«ããã°ã倿ããŒãã«ãè
ãããšã¯ãããŸããããããã³ã°ãå¿
èŠãªã hairForSource() ã®ãããªå°çšé¢æ°ã«éã蟌ããåŒã³åºãåŽã¯ãèå¥åããã®ãŸãŸæž¡ããã ãã«ããã
â¡ å®æ°ããŒãã«ã¯ Object.freeze() ã§ä¿è·ãã
è€æ°ãã¡ã€ã«ãã import ããã宿°ãªããžã§ã¯ã㯠Object.freeze() ãããããã€ã³ããŒãåŽããã®èª€ä»£å
¥ã TypeError ã§å³åº§ã«æ€ç¥ã§ããŸããç¹ã« Codex ã«å®è£
ãå§è²ããŠãããšããCodex ããå©äŸ¿æ§ã®ããã«ãããŒãã«ãæ¡åŒµããã³ãŒããæžãå¯èœæ§ããããããç©ççã«å€æŽã§ããªãç¶æ
ã«ããŠããããšã§ãæå³ããªãæ¡åŒµããé²ããŸãã
⢠ãã©ãŒã«ããã¯ã®ãåãééãããéçºç°å¢ã§ã ã峿 Œã«ãã
ãã©ãŒã«ããã¯ãæ¬çªãšåãç¶æ
ïŒä»åã¯ã赀髪ã§åºããïŒã§åäœããå Žåãgoing-forward ãã°ã®çãæ®ããšãã©ãŒã«ããã¯çºç«ãåºå¥ã§ããªããªããŸããprocess.env.STRICT_HAIR_SOURCE = '1' ã®ãããªç°å¢å€æ°ã§ãæªç¥ã®ãœãŒã¹ãæ¥ããå³äŸå€ãã¢ãŒãã«åãæ¿ããããèšèšã«ããŠããããã¹ãå®è¡æã¯ strict ã¢ãŒãã§èµ°ããããšãä¿®æ£ãæ£ããæ©èœããŠãããã確èªãããããªããŸãã
⣠going-forward ä¿®æ£ â æ°èŠããŒã¿ã§åäœç¢ºèª â éå»ããŒã¿ä¿®æ£ã®é çªãå®ã
ãã°ä¿®æ£ãšéå»ããŒã¿ã®ä¿®æ£ã¯ããã°ä¿®æ£ãæ°èŠããŒã¿ã«æ£ããåæ ãããŠãããšããšã確èªããŠãããéå»ããŒã¿ãçŽããã®é çªãå®ããéã®é åºã§äœæ¥ãããšãretrofit å®äºåŸãå€ãã³ãŒããåãç¶ããŠèª€ã£ãããŒã¿ãåçç£ãããä¿®æ£æžã¿ã®ã¯ãã®èšäºãç¿æ¥ã«ã¯å ã«æ»ããŸããã1æ¬ã ãæ¬çªã§åäœç¢ºèªããŠãã倧éåŠçã«ç§»ãããšããäžéã¹ããããã«ãŒã«åããã
†ãããåŠçã¯ãåªç + state JSON + --reset ãã©ã°ããæåããèšèšã«å ¥ãã
state/retrofit-hair-*.json ã®ãããªé²æãã¡ã€ã«ãæåããèšèšã«å«ããåå®è¡ãããšæªåŠçåã ããå®è¡ãããåªçèšèšã«ãããåãã㊠--reset ãã©ã°ã§ state JSON ããŒãã¯ãªã¢ããŠããåå®è¡ã§ãããªãã·ã§ã³ã--only-keys key1,key2 ã§ç¹å®ã®ã¢ã€ãã ã ãåŠçã§ãããªãã·ã§ã³ãçšæããŠãããåŸä»ãã§åªçèšèšã远å ããã»ã©ãstate JSON ã®ãã©ãŒããããšæ¢åããžãã¯ã®æŽåãåãä¿®æ£ã³ã¹ããäžãããŸãã
⥠CLI åŒæ°ããŒãµãŒã¯ parse çŽåŸã« NaN ãã§ãã¯ãå ¥ãã
opts.limit = parseInt(argv[i + 1]);
if (isNaN(opts.limit)) throw new Error(`--limit ã®æ¬¡ã«æ°å€ãå¿
èŠã§ã: ${argv[i + 1]}`);
parseInt(undefined) 㯠NaN ãè¿ããcount >= NaN ã¯åžžã« false ã§ããã©ã®æ°å€ãã倧ãããŠãå€å®ãæç«ããªãã®ã§ãäžéããªããªããŸããåŒæ° parse ã¯ããŠãŒã¶ãŒå
¥åã®ããªããŒã·ã§ã³ããšåçã®ã¬ãã«ã§æ±ããåãã§ãã¯ãšå³ãšã©ãŒã parse çŽåŸã«å
¥ããã
⊠Playwright ãããã¡ã€ã«ã®ç«¶åã¯äºåæ€ç¥ããŠããŒãªã³ã°ã§åé¿ãã
Chromium ã¯åããããã¡ã€ã«ãã£ã¬ã¯ããªãè€æ°ããã»ã¹ãåæã«éããŸããããããå®è¡ã®åã« ps aux ã§åœè©²ãããã¡ã€ã«ã®ãã¹æååãæ€çŽ¢ããèŠã€ãã£ãã60ç§ããŒãªã³ã°ã§ç©ºããåŸ
ã€èšèšã«ãããæå€§åŸ
æ©æéïŒä»åã¯30åïŒãèšå®ããŠã¿ã€ã ã¢ãŠãã§äŸå€ãæããããšã§ãæ°žä¹
åŸ
æ©ãé²ãããã®ãã¿ãŒã³ã¯ãåããªãœãŒã¹ãè€æ°ã¹ã¯ãªããã奪ãåããç¶æ³å
šè¬ã«è»¢çšã§ããŸãã
â§ çµµæåã»éå¯Ÿå¿æåã®é€å»ã¯ãæç»ãã€ãã©ã€ã³ã®å ¥å£ãã«çœ®ã
sanitize_text() ãåŒã³åºãåŽã«çœ®ããšãgoing-forwardã»retrofitã»æåå®è¡ã®ãã¹ãŠã®çµè·¯ã§åã颿°ãåŒã¶å¿
èŠãçããŸããæç»ããžãã¯ã®å
éšå
¥å£ïŒä»å㯠overlay_note_text.py ã® render() åé ïŒã«çœ®ãã°ãã©ã®çµè·¯ããåŒã°ããŠãèªåçã«é©çšãããŸãããé²åŸ¡åŠçã¯å
¥å£ã«è¿ãæ¹ãã眮ãããšããååã®å
·äœäŸã§ãã
âš èšå®ã®1ç¹éäžç®¡çã§ã¹ã¯ãªããéã®ãæžãæãæŒãããé²ã
ã¬ãŒã³ã»ãããã¡ã€ã«ã»ã¢ã«ãŠã³ã URL ã®å¯Ÿå¿è¡šã¯ config/comment-lanes.json ã®ãããª1ã€ã®ãã¡ã€ã«ã«éçŽãããåã¹ã¯ãªããã¯ãã®ãã¡ã€ã«ãèªãã§èªåã®ãããã¡ã€ã«ãã¹ãåçã«åŒãèšèšã«ãããæ°ããã¬ãŒã³ã远å ãããšãããã¡ã€ã«1ã€ãæŽæ°ããã ãã§å
šã¹ã¯ãªããã«èšå®ãäŒãããŸããããŒãã³ãŒãã¯ã倿Žå
ããšã倿޿Œãç®æããåæã«çã¿ãŸãã
â© ãã¹ã¯ãªãããæ£åžžçµäºããããšãæåŸ ããææç©ãæ£ãããã¯å¥ã®ã¢ãµãŒã·ã§ã³
swap-eyecatch.js ã® exit 0 ãšãã¢ããããŒããããµã ããæ£ãã髪è²ã«ãªã£ãŠãããã¯å¥åœé¡ã§ãã倧éåŠçã¹ã¯ãªããã«ã¯ãåŠçä»¶æ°ãšå®éã®ææç©ãçªãåãããäºåŸæ€èšŒã¹ãããããå¿
ãå«ãããä»åã¯ç®èŠç¢ºèªã§ããããã¹ã±ãŒã«ããåã«ãå·®ãæ¿ãåŸã®ç»å URL ã fetch ããŠãã¯ã»ã«å±æ§ãæ€æ»ããã¹ãããããçµã¿èŸŒãã§ããããšã§ãå°æ¥ã®100æ¬ã»300æ¬ã®åŠçã§ãå質ãæ
ä¿ã§ããŸãã
⪠ãã©ã³ãèšèšã¯ã³ãŒãããå ã«ç¢ºå®ããã
ãåã¢ã«ãŠã³ãã®ã¢ã€ã³ã³ã®é«ªè²ã¯äœè²ãããšãããã©ã³ãå®çŸ©ãå
ã«ãã£ãŠãHAIR_BY_SOURCE ããŒãã«ã¯ãã®å®è£
ã§ããå®çŸ©ãåŸããå€ãããšãä»åã®ãããªå€§éã¬ãããã£ãããå¿
èŠã«ãªããŸãã5ã¢ã«ãŠã³ãã䞊走ãããåã«ãåã¢ã«ãŠã³ãã®ã«ã©ãŒã¢ã€ãã³ãã£ãã£ããæ±ºããã³ãŒãã«åæ ãããé çªãæ£ãããèšäºãéç£ããŠãããã©ã³ããå€ããããšã®è¿æžã³ã¹ããæåããæèããŠããã
â« going-forward ãšéå»ããŒã¿ã®äž¡æ¹ã«ä¿®æ£ãé©çšãã責任ãæåããæ³å®ãã
ä»åã®ãããªãèå¥åã®èª€ãããã³ã°ããã°ãèªåæçš¿ãã€ãã©ã€ã³ã«æœãã§ããå Žåãä¿®æ£ã«ã¯ going-forward ã®1è¡ãšãéå»ããŒã¿ã®ã¬ãããã£ããã®2ã€ãå¿ èŠã§ãããã³ãŒãã1è¡çŽãããçµãããã§ã¯ãªããéå»ã«ç©ã¿äžãã£ãããŒã¿ãçŽãå¿ èŠãããããšããèŠç¹ãæåããæã£ãŠãããšãä¿®æ£èšç»ãç«ãŠããããªããŸããç¹ã«æ¯æ¥æ°åæ¬ãèªåçæãããã€ãã©ã€ã³ã§ã¯ããã°ãæœãã§ããæéã®ããŒã¿ä»¶æ°ãå³åº§ã«èŠç©ãããç¶æ ã«ããŠããã
⬠ææã«ããŒãšç¡æãµã ãã§å®çŸ©ãå ±æãã
hairForSource() 㯠gen-codex-thumbnail.mjsïŒç¡æãµã ãïŒãš gen-paid-cover.mjsïŒææã«ããŒïŒã®äž¡æ¹ãã import ãããŠããŸããè²ã®å®çŸ©ã1ç®æã«éããããšã§ã5è²ã®ãã¡1è²ãå€ãããšã㯠HAIR_BY_SOURCE ããŒãã«ã®1è¡ãçŽãã ãã§äž¡ç³»çµ±ã«åæ ãããŸããå®çŸ©ã®éè€ã¯ãçæ¹ã ãçŽããŠæŽåãåããªããªããäºæ
ã®æž©åºã§ããçæç³»ã¹ã¯ãªãããè€æ°äžŠèµ°ããããšãã¯ãå
±éããèšå®å€ã®ç®¡çå Žæãæåã«æ±ºããŠããã
ãŸãšã
ä»åãã£ãããšãã²ãšããšã§èšãã°ãã1è¡ã®ãã°ãä¿®æ£ããããã«300è¡è¶ ã®ã¹ã¯ãªãããæžãããã§ãã
daily-codex-note.js ã®èª€ãããã³ã°ïŒbuild â 'note'ãaffiliate â 'note2'ïŒã¯ãå®è£
åœæã®å€æãã¹ã§ããHAIR_BY_SOURCE ããŒãã«ãååšããªãã£ãæä»£ã«æžãã倿ããžãã¯ããããŒãã«ã远å ããããšãçãæ®ã£ãŠãããã仿§ãå€ãã£ãã®ã«ã³ãŒããå€ãããªãã£ãããšãããèªååãã€ãã©ã€ã³ã§ç¹ã«ããèµ·ãããã¿ãŒã³ã§ãã
åé¡ã®æ ¹æ·±ãã¯ãèªååãã€ãã©ã€ã³ããééã£ã仿§ã§ãæ¯æ¥åãç¶ãããããšã«ãããŸããæã§èšäºãæžããŠããã°ããã®ãµã ããè²ãããããããšæ°ã¥ãæ©äŒããããŸããã§ã1æ¥10æ¬ã®ãµã ããèªåã§çæãããŠãããšãåºãŠãããã®ãäžæ¬äžæ¬ç¢ºèªããã³ã¹ããé«ãããŠèŠèœãšããç¶ããæ°ã¥ãããšãã«ã¯4ã5æ¥åã44æ¬ã®è² åµãç©ã¿äžãã£ãŠããŸãã
ä¿®æ£ãã®ãã®ã¯ hairForSource() ã®20è¡ãš source = lane.key ã®1è¡ã§ããããããå®éã«åé¡ã解決ãããŸã§ã«å¿
èŠã ã£ãã®ã¯ãå
¬éèšäºäžèЧã®ååŸããžãã¯ãåªç㪠state JSONãPlaywright ã®ãããã¡ã€ã«è¡çªæ€ç¥ãçµµæåé€å»ãåŒæ°ããŒãµãŒã® NaN ãã§ãã¯ââãšããããã°ãè¿æžããããã®åšèŸºã³ãŒããã§ããã¬ãããã£ããã¹ã¯ãªãã2æ¬ã®åèšè¡æ°ã¯300è¡è¶
ã«ãªããŸããã
ãã®ã³ã¹ãã®é察称æ§ãä»åã®äžçªã®æèšã§ããèªååã®ã¹ã«ãŒããããé«ãã»ã©ãèšèšãã¹ã®è¿æžã³ã¹ããé«ããªããæå120äžãšããæ°åã®è£åŽã«ã¯ããããããééããŠããçŽããåŸåŸ©ãäœååããããŸãããåããŠãããã倧äžå€«ããšãã倿ãäœé±éãç¶ããçµæããããšããŸãšãŸã£ãè² åµãé¡åšåããã
ããã¯èªååã«éã£ã話ã§ã¯ãããŸããããèªååã¯ç¹ã«ãã®å¹æãå¢å¹
ãããŸããObject.freeze() ã§å®æ°ãä¿è·ãããNaN ããã§ãã¯ãããstate JSON ã§åªçæ§ãæ
ä¿ããããããã¡ã€ã«è¡çªãäºåæ€ç¥ããââäžã€äžã€ã¯å°å³ãªäœæ¥ã§ããã§ããããç©ã¿äžãã£ãŠããµã ããæ¯æ¥æ£ãã髪è²ã§5ã¢ã«ãŠã³ãååºãŠããããšããç¶æ
ãç¶æãããŸãã
æå120äžãç¶ç¶ãããã®ã¯ãæåã«åããä»çµã¿ãã§ã¯ãªãããå£ãããšãã«çŽ æ©ãçŽããä»çµã¿ãã§ããã³ããã 7b78ca5 ã®1è¡ä¿®æ£ã¯ããã«æžããŸãããã44æ¬ã®ã¬ãããã£ãããå®å
šã«å®è¡ã§ããåªçãªåºç€ããªããã°ãä¿®æ£äžã«å¥ã®ãã°ãèžãã§ããã«å€§ããªè² åµã«ãªã£ãŠããŸãããèªååã®å質ã¯ãã©ãã ãéãåãããã§ã¯ãªããå£ãããšãã«ã©ãã ãå¶åŸ¡ãããæ¹æ³ã§çŽããããã§æ±ºãŸããšä»åæ¹ããŠå®æããŸããã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸã ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ