LOADING
search, detail, new dll info lengkap cek README.md
#!/usr/bin/env node
/**
* dafont.js — ESM scraper/wrapper dafont.com (CLI + module)
*
* CLI: node dafont.js search|new|top|category|author|font|url <args>
* Module: import { search, getFont, getNewFonts, ... } from './dafont.js'
*/
import * as cheerio from 'cheerio';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const BASE = 'https://www.dafont.com';
const DL = 'https://dl.dafont.com';
const H = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
Accept: 'text/html',
'Accept-Language': 'en-US,en;q=0.9',
};
const get = async (url) => {
const r = await fetch(url, { headers: H });
if (!r.ok) throw new Error(`HTTP ${r.status} → ${url}`);
return r.text();
};
export const delay = (ms = 1200) => new Promise((r) => setTimeout(r, ms));
const num = (s) => {
const m = String(s || '').match(/([\d,.]+)/);
return m ? parseInt(m[1].replace(/[.,]/g, ''), 10) : null;
};
const abs = (u) => !u ? null : u.startsWith('//') ? 'https:' + u : u.startsWith('http') ? u : BASE + (u.startsWith('/') ? u : '/' + u);
const licenseOf = (t) =>
/100%\s*Free/i.test(t) ? '100% Free'
: /Free for personal use/i.test(t) ? 'Free for personal use'
: /Public domain|GPL|OFL/i.test(t) ? 'Public domain / GPL / OFL'
: /Donationware/i.test(t) ? 'Donationware' : null;
/** Parse list pages (new/top/category/author/search) */
function parseList(html) {
const $ = cheerio.load(html);
const out = [], seen = new Set();
$('div.lv1left').each((_, el) => {
const $l = $(el);
// slug: dari link .font di dalam, atau dari preview sibling
let slug = null;
const $a = $l.find('a[href$=".font"]').first();
if ($a.length) slug = ($a.attr('href') || '').replace(/^\//, '').replace(/\.font$/, '');
if (!slug) {
const $p = $l.nextAll('div.preview').first().find('a[href$=".font"]').first();
if ($p.length) slug = ($p.attr('href') || '').replace(/^\//, '').replace(/\.font$/, '');
}
if (!slug || seen.has(slug)) return;
seen.add(slug);
// name: strong / highlight / link text
const name =
$l.find('a[href$=".font"] strong').text().trim() ||
$l.find('span.highlight').text().trim() ||
$a.text().trim() ||
slug;
const $auth = $l.find('a[href*=".d"]').first();
const author = $auth.length ? $auth.text().trim() : null;
const authorSlug = $auth.length ? ($auth.attr('href') || '').replace(/^\//, '') : null;
const $r = $l.nextAll('div.lv1right').first();
const $v = $l.nextAll('div.lv2right').first();
const $d = $l.nextAll('div.dlbox').first();
const $pr = $l.nextAll('div.preview').first();
const cats = [];
$r.find('a').each((__, a) => { const t = $(a).text().trim(); if (t && t !== 'help') cats.push(t); });
const light = $v.find('span.light').text() || $v.text();
const downloads = num(light);
const y = light.match(/\(([\d,.]+)\s+yesterday\)/i);
const downloadsYesterday = y ? num(y[1]) : null;
const license = licenseOf($v.find('a.help').text() || $v.text());
const dlHref = $d.find('a.dl').attr('href') || '';
const downloadUrl = abs(dlHref) || `${DL}/dl/?f=${slug.replace(/-/g, '_')}`;
let preview = null;
const bg = ($pr.attr('style') || '').match(/url\((['"]?)([^)'"]+)\1\)/);
if (bg) preview = abs(bg[2]);
out.push({
name, slug,
url: `${BASE}/${slug}.font`,
downloadUrl, preview,
downloads, downloadsYesterday, license,
author, authorSlug,
categories: cats.length ? cats : null,
});
});
return out;
}
export async function search(q, page = 1) {
const qs = encodeURIComponent(q.trim());
const url = `${BASE}/search.php?q=${qs}${page > 1 ? `&page=${page}` : ''}`;
return parseList(await get(url));
}
export async function getNewFonts(page = 1) {
return parseList(await get(page === 1 ? `${BASE}/new.php` : `${BASE}/new.php?page=${page}`));
}
export async function getTopFonts(page = 1, period = 'yesterday') {
let url = `${BASE}/top.php`;
if (period === 'alltime') url += '?period=2';
if (page > 1) url += (url.includes('?') ? '&' : '?') + `page=${page}`;
return parseList(await get(url));
}
export async function getCategoryFonts(catId, page = 1) {
const url = `${BASE}/theme.php?cat=${catId}${page > 1 ? `&page=${page}` : ''}`;
return parseList(await get(url));
}
export async function getAuthorFonts(authorSlug, page = 1) {
const s = authorSlug.replace(/^\//, '');
return parseList(await get(page === 1 ? `${BASE}/${s}` : `${BASE}/${s}?page=${page}`));
}
export async function getFont(slug) {
const clean = slug.replace(/\.font$/, '').replace(/^\//, '');
const url = `${BASE}/${clean}.font`;
const $ = cheerio.load(await get(url));
const name =
$('div.lv1left a[href$=".font"] strong').first().text().trim() ||
$('div.lv1left span.highlight').text().trim() ||
clean;
let author = null, authorSlug = null;
$('div.lv1left a[href*=".d"]').each((_, el) => {
const h = $(el).attr('href') || '';
if (/login|register/i.test(h)) return;
author = $(el).text().trim();
authorSlug = h.replace(/^\//, '');
return false;
});
const categories = [];
$('div.lv1right a').each((_, el) => {
const t = $(el).text().trim();
const h = $(el).attr('href') || '';
if (t && t !== 'help') categories.push({ name: t, url: abs(h) });
});
const light = $('div.lv2right span.light').text() || $('div.lv2right').text();
const downloads = num(light);
const y = light.match(/\(([\d,.]+)\s+yesterday\)/i);
const downloadsYesterday = y ? num(y[1]) : null;
const license = licenseOf($('div.lv2right a.help').text() || $('div.lv2right').text());
const dlHref = $('div.dlbox a.dl').attr('href') || '';
const downloadUrl = abs(dlHref) || `${DL}/dl/?f=${clean.replace(/-/g, '_')}`;
const files = [];
$('span.filename').each((_, el) => { const t = $(el).text().trim(); if (t) files.push(t); });
if (!files.length) $('b').each((_, el) => { const t = $(el).text().trim(); if (/\.(ttf|otf|fon)$/i.test(t)) files.push(t); });
let preview = null;
const bg = ($('div.preview').attr('style') || '').match(/url\((['"]?)([^)'"]+)\1\)/);
if (bg) preview = abs(bg[2]);
const ill = $('img[src*="/img/illustration/"]').attr('src');
const illustration = ill ? abs(ill) : null;
let firstSeen = null;
const fs = $('div.dfsmall').filter((_, el) => /First seen on DaFont/i.test($(el).text())).first().text();
const fm = fs.match(/First seen on DaFont:\s*(.+)/i);
if (fm) firstSeen = fm[1].trim();
// note: ambil div setelah "Note of the author"
let note = null;
$('div').each((_, el) => {
if (/Note of the author/i.test($(el).text()) && $(el).find('i b, b i').length) {
const n = $(el).next('div').text().replace(/\s+/g, ' ').trim();
if (n && n.length < 800 && !/Cookies|Privacy Policy/i.test(n)) note = n;
return false;
}
});
return {
name, slug: clean, url, downloadUrl, preview, illustration,
author, authorSlug, categories, downloads, downloadsYesterday,
license, files: [...new Set(files)], firstSeen, note,
};
}
export function getDownloadUrl(slug) {
return `${DL}/dl/?f=${slug.replace(/\.font$/, '').replace(/^\//, '').replace(/-/g, '_')}`;
}
// ---------- CLI ----------
const isCLI = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isCLI) {
const [, , cmd, ...args] = process.argv;
const j = (d) => console.log(JSON.stringify(d, null, 2));
const die = (m) => { console.error(m); process.exit(1); };
(async () => {
try {
switch (cmd) {
case 'search':
if (!args[0]) die('Usage: node dafont.js search <query> [page]');
j(await search(args[0], +args[1] || 1));
break;
case 'new':
j(await getNewFonts(+args[0] || 1));
break;
case 'top':
j(await getTopFonts(+args[0] || 1, args[1] === 'alltime' ? 'alltime' : 'yesterday'));
break;
case 'category':
case 'cat':
if (!args[0]) die('Usage: node dafont.js category <catId> [page]');
j(await getCategoryFonts(args[0], +args[1] || 1));
break;
case 'author':
if (!args[0]) die('Usage: node dafont.js author <authorSlug> [page]');
j(await getAuthorFonts(args[0], +args[1] || 1));
break;
case 'font':
case 'detail':
if (!args[0]) die('Usage: node dafont.js font <slug>');
j(await getFont(args[0]));
break;
case 'url':
if (!args[0]) die('Usage: node dafont.js url <slug>');
console.log(getDownloadUrl(args[0]));
break;
default:
console.log(`
dafont.js — dafont.com scraper (CLI + module)
search <query> [page]
new [page]
top [page] [yesterday|alltime]
category <catId> [page]
author <authorSlug> [page]
font <slug>
url <slug>
Contoh:
node dafont.js search coolvetica
node dafont.js new 1
node dafont.js top 1 alltime
node dafont.js font super-bouncer
`);
}
} catch (e) {
console.error('Error:', e.message);
process.exit(1);
}
})();
}Scraper / wrapper Node.js (ESM) untuk dafont.com.
Bisa dipakai sebagai CLI maupun di-import sebagai module.
| Fitur | Keterangan |
|---|---|
| Search | Cari font berdasarkan kata kunci |
| New | Daftar font terbaru |
| Top | Font terpopuler (yesterday / all-time) |
| Category | Font per kategori (pakai cat id) |
| Author | Semua font dari satu author |
| Detail | Info lengkap 1 font (downloads, license, preview, files, dll) |
| URL download | Ambil link ZIP langsung |
Butuh Node.js 18+ (karena pakai native fetch).
bashnpm install cheerio
Simpan file dafont.js di folder project-mu.
bashnode dafont.js <command> [args]
| Command | Argumen | Contoh |
|---|---|---|
search | <query> [page] | node dafont.js search coolvetica |
new | [page] | node dafont.js new 1 |
top | [page] [yesterday|alltime] | node dafont.js top 1 alltime |
category | <catId> [page] | node dafont.js category 101 |
author | <authorSlug> [page] | node dafont.js author mans-greback.d2878 |
font | <slug> | node dafont.js font super-bouncer |
url | <slug> | node dafont.js url super-bouncer |
Search
bashnode dafont.js search coolvetica
json[ { "name": "Coolvetica", "slug": "coolvetica", "url": "https://www.dafont.com/coolvetica.font", "downloadUrl": "https://dl.dafont.com/dl/?f=coolvetica", "preview": "https://www.dafont.com/img/preview/c/o/coolvetica3.png", "downloads": 16423642, "downloadsYesterday": 4698, "license": "Free for personal use", "author": "Typodermic Fonts", "authorSlug": "typodermic-fonts.d1705", "categories": ["Basic", "Sans serif"] } ]
Detail font
bashnode dafont.js font super-bouncer
json{ "name": "Super Bouncer", "slug": "super-bouncer", "url": "https://www.dafont.com/super-bouncer.font", "downloadUrl": "https://dl.dafont.com/dl/?f=super_bouncer", "preview": "https://www.dafont.com/img/preview/s/u/super_bouncer0.png", "illustration": "https://www.dafont.com/img/illustration/s/u/super_bouncer.png", "author": "fsuarez913", "authorSlug": "fsuarez913.d3946", "categories": [ { "name": "Fancy", "url": "https://www.dafont.com/mtheme.php?id=1" }, { "name": "Cartoon", "url": "https://www.dafont.com/theme.php?cat=101" } ], "downloads": 129422, "downloadsYesterday": 3361, "license": "100% Free", "files": ["Super Bouncer.ttf"], "firstSeen": "July 14, 2026", "note": "Free for personal use & commercial use. ..." }
jsimport { search, getNewFonts, getTopFonts, getCategoryFonts, getAuthorFonts, getFont, getDownloadUrl, delay, } from './dafont.js'; // Cari font const hasil = await search('pixel', 1); console.log(hasil); // Font baru const baru = await getNewFonts(1); // Top all-time const top = await getTopFonts(1, 'alltime'); // Kategori Cartoon (cat=101) const cartoon = await getCategoryFonts(101, 1); // Detail const detail = await getFont('super-bouncer'); // URL download saja const url = getDownloadUrl('super-bouncer'); // → https://dl.dafont.com/dl/?f=super_bouncer // Jeda antar request (disarankan) await delay(1500);
| Field | Tipe | Keterangan |
|---|---|---|
name | string | Nama font |
slug | string | Identifier (untuk URL & download) |
url | string | Halaman font di dafont |
downloadUrl | string | Link ZIP |
preview | string | null | URL thumbnail preview |
downloads | number | null | Total download |
downloadsYesterday | number | null | Download kemarin |
license | string | null | Jenis lisensi |
author | string | null | Nama author |
authorSlug | string | null | Slug author |
categories | string[] | null | Kategori |
font)Semua field di atas, plus:
| Field | Tipe | Keterangan |
|---|---|---|
illustration | string | null | Gambar ilustrasi besar |
files | string[] | Nama file di dalam ZIP (.ttf / .otf) |
firstSeen | string | null | Tanggal pertama muncul di dafont |
note | string | null | Catatan dari author |
categories | { name, url }[] | Kategori + link |
| ID | Kategori |
|---|---|
101 | Fancy → Cartoon |
103 | Fancy → Groovy |
110 | Fancy → Horror |
501 | Basic → Sans serif |
601 | Script → Calligraphy |
603 | Script → Handwritten |
604 | Script → Brush |
606 | Script → Graffiti |
Lihat semua tema di dafont.com/themes.php.
delay(1000–2000)) agar tidak membebani server./search.php di-disallow di robots.txt — pakai secukupnya.dl.dafont.com) valid; buka lewat browser dari halaman font biasanya langsung dapat ZIP.M�ns) karena charset halaman sumber (ISO-8859-1).license hanya indikasi dari dafont.Bebas dipakai & dimodifikasi.