← back to domovinatv__fetch.domovina.tv

Function bodies 195 total

All specs Real LLM only Function bodies
main function · python · L211-L247 (37 LOC)
diarize.py
def main():
    args = parse_args()

    # Provjera ulaznih datoteka
    if not os.path.exists(args.wav):
        print(f"❌ WAV datoteka ne postoji: {args.wav}")
        sys.exit(1)
    if not os.path.exists(args.srt):
        print(f"❌ SRT datoteka ne postoji: {args.srt}")
        sys.exit(1)

    print(f"   📄 WAV: {os.path.basename(args.wav)}")
    print(f"   📄 SRT: {os.path.basename(args.srt)}")

    # 1. Parsiraj postojeći SRT (od whisper.cpp)
    srt_segments = parse_srt(args.srt)
    print(f"   📝 Parsirano {len(srt_segments)} SRT segmenata")

    if len(srt_segments) == 0:
        print("❌ SRT datoteka je prazna ili neispravan format!")
        sys.exit(1)

    # 2. Pokreni pyannote diarizaciju (na MPS/Metal GPU)
    speaker_segments = run_diarization(
        args.wav,
        args.hf_token,
        device=args.device,
        min_speakers=args.min_speakers,
        max_speakers=args.max_speakers
    )

    # 3. Pridruži govornika svakom SRT segmentu
    srt_segments = assign_sp
sleep function · javascript · L69-L71 (3 LOC)
fetch.js
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
sanitizeDescription function · javascript · L73-L84 (12 LOC)
fetch.js
function sanitizeDescription(str) {
  if (!str) return "nepoznat_naslov";
  str = str.toLowerCase();
  const map = {
    'č': 'c', 'ć': 'c', 'ž': 'z', 'š': 's', 'đ': 'd',
    'Č': 'c', 'Ć': 'c', 'Ž': 'z', 'Š': 's', 'Đ': 'd'
  };
  str = str.replace(/[čćžšđČĆŽŠĐ]/g, (char) => map[char] || char);
  str = str.replace(/[^a-z0-9]/g, '_');
  str = str.replace(/_+/g, '_').replace(/^_|_$/g, '');
  return str || "nepoznat_naslov";
}
extractVideoId function · javascript · L86-L91 (6 LOC)
fetch.js
function extractVideoId(url) {
  url = url.trim();
  if (!url) return null;
  const m = url.match(/(?:youtu\.be\/|v=)([a-zA-Z0-9_-]{11})/);
  return m ? m[1] : null;
}
extractDataFromLine function · javascript · L93-L110 (18 LOC)
fetch.js
function extractDataFromLine(line) {
  line = line.trim();
  if (!line || line.startsWith("#")) return null;
  if (line.includes("|")) {
    const parts = line.split("|");
    const url = parts[parts.length - 1].trim();
    let title = "nepoznat_naslov";
    let date = "NA";
    if (parts.length >= 3) {
      date = parts[0].trim();
      title = parts.slice(1, parts.length - 1).join(" ").trim();
    } else if (parts.length === 2) {
      title = parts[0].trim();
    }
    return { url, title, date };
  }
  return { url: line, title: "nepoznat_naslov", date: "NA" };
}
loadState function · javascript · L112-L124 (13 LOC)
fetch.js
function loadState(stateFile) {
  if (fs.existsSync(stateFile)) {
    try {
      const state = JSON.parse(fs.readFileSync(stateFile, "utf-8"));
      // Osiguraj da 'private' polje postoji (kompatibilnost sa starijim state datotekama)
      if (!Array.isArray(state.private)) state.private = [];
      return state;
    } catch (e) {
      console.error(`[GREŠKA] Neispravan JSON stanja: ${stateFile}`);
    }
  }
  return { completed: [], failed: [], private: [] };
}
saveState function · javascript · L126-L130 (5 LOC)
fetch.js
function saveState(stateFile, state) {
  const tempFile = stateFile + ".tmp";
  fs.writeFileSync(tempFile, JSON.stringify(state, null, 2));
  fs.renameSync(tempFile, stateFile);
}
Repobility — the code-quality scanner for AI-generated software · https://repobility.com
downloadVideo function · javascript · L132-L172 (41 LOC)
fetch.js
function downloadVideo(videoId, outputDir, filenameTemplate) {
  fs.mkdirSync(outputDir, { recursive: true });
  const finalTemplate = path.join(outputDir, filenameTemplate + ".%(ext)s");

  const args = [
    ...YT_DLP_BASE_ARGS,
    "-o", finalTemplate,
    `https://www.youtube.com/watch?v=${videoId}`,
  ];

  return new Promise((resolve, reject) => {
    // Capture stderr da detektiramo private/unavailable video greške
    const proc = spawn("yt-dlp", args, {
      stdio: ["inherit", "inherit", "pipe"]  // stdin+stdout inherit, stderr capture
    });

    let stderrOutput = "";
    proc.stderr.on("data", (chunk) => {
      const text = chunk.toString();
      stderrOutput += text;
      // Ispiši stderr u realnom vremenu (kao i prije)
      process.stderr.write(text);
    });

    proc.on("close", (code) => {
      if (code === 0) {
        resolve();
      } else {
        const err = new Error(`yt-dlp exit code: ${code}`);
        // Detektiraj private/unavailable video
        if
ChannelQueue class · javascript · L174-L295 (122 LOC)
fetch.js
class ChannelQueue {
  constructor(filePath, baseOutputDir) {
    this.filePath = filePath;
    this.baseOutputDir = baseOutputDir;
    const filename = path.basename(filePath).replace("-lista.txt", "").replace(".txt", "");
    this.channelName = sanitizeDescription(filename);
    this.outputDir = path.join(baseOutputDir, this.channelName);
    this.stateFile = filePath.replace(".txt", "-state.json");
    this.state = loadState(this.stateFile);
    this.pendingVideos = [];
    this.isExhausted = false;
    this.init();
  }

  init() {
    const rawLines = fs.readFileSync(this.filePath, "utf-8").split("\n");
    const entries = rawLines
      .map((line) => {
        const data = extractDataFromLine(line);
        if (!data) return null;
        const videoId = extractVideoId(data.url);
        if (!videoId) return null;

        const safeTitle = sanitizeDescription(data.title);
        let filenameTemplate = "";

        if (data.date && /^\d{8}$/.test(data.date) && data.date !== "NA"
constructor method · javascript · L175-L186 (12 LOC)
fetch.js
  constructor(filePath, baseOutputDir) {
    this.filePath = filePath;
    this.baseOutputDir = baseOutputDir;
    const filename = path.basename(filePath).replace("-lista.txt", "").replace(".txt", "");
    this.channelName = sanitizeDescription(filename);
    this.outputDir = path.join(baseOutputDir, this.channelName);
    this.stateFile = filePath.replace(".txt", "-state.json");
    this.state = loadState(this.stateFile);
    this.pendingVideos = [];
    this.isExhausted = false;
    this.init();
  }
init method · javascript · L188-L218 (31 LOC)
fetch.js
  init() {
    const rawLines = fs.readFileSync(this.filePath, "utf-8").split("\n");
    const entries = rawLines
      .map((line) => {
        const data = extractDataFromLine(line);
        if (!data) return null;
        const videoId = extractVideoId(data.url);
        if (!videoId) return null;

        const safeTitle = sanitizeDescription(data.title);
        let filenameTemplate = "";

        if (data.date && /^\d{8}$/.test(data.date) && data.date !== "NA") {
          filenameTemplate = `${data.date}_${safeTitle}_yt_${videoId}`;
        } else {
          filenameTemplate = `%(upload_date)s_${safeTitle}_yt_${videoId}`;
        }

        return { line, url: data.url, videoId, title: data.title, filenameTemplate };
      })
      .filter((e) => e && e.videoId);

    const uniqueMap = new Map();
    entries.forEach((e) => uniqueMap.set(e.videoId, e));
    this.pendingVideos = Array.from(uniqueMap.values()).filter(e =>
      !this.state.completed.includes(e.videoId) &&
      !t
processBatch method · javascript · L220-L294 (75 LOC)
fetch.js
  async processBatch(batchSize) {
    if (this.pendingVideos.length === 0) {
      this.isExhausted = true;
      return 0;
    }

    const batch = this.pendingVideos.slice(0, batchSize);
    let successCount = 0;

    console.log(`\n🔵 [${this.channelName.toUpperCase()}] Batch... (Preostalo: ${this.pendingVideos.length})`);

    for (let i = 0; i < batch.length; i++) {
      const video = batch[i];
      const logName = video.filenameTemplate.startsWith("%")
        ? `[Auto-Date] ...${video.title.substring(0, 30)}...`
        : video.filenameTemplate;

      console.log(`   ➡️  [${i + 1}/${batch.length}] Cilj: "${logName}"`);

      try {
        await downloadVideo(video.videoId, this.outputDir, video.filenameTemplate);

        if (globalConsecutiveErrors > 0) {
          console.log(`   ✨ [OPORAVAK] Resetiram brojač grešaka.`);
        }
        globalConsecutiveErrors = 0;

        this.state.completed.push(video.videoId);
        if (this.state.failed.includes(video.videoId)) {
main function · javascript · L297-L326 (30 LOC)
fetch.js
async function main() {
  const args = process.argv.slice(2);
  const outputDirIdx = args.indexOf("--output-dir");
  const baseOutputDir = outputDirIdx !== -1 ? args[outputDirIdx + 1] : DEFAULT_OUTPUT_DIR;

  if (!fs.existsSync(LISTS_DIR)) { console.error(`Nema direktorija: ${LISTS_DIR}`); process.exit(1); }
  const listFiles = fs.readdirSync(LISTS_DIR).filter((f) => f.endsWith("-lista.txt")).map((f) => path.join(LISTS_DIR, f));

  console.log("--- Inicijalizacija ---");
  let channels = listFiles.map((file) => new ChannelQueue(file, baseOutputDir));
  let activeChannels = channels.filter((c) => !c.isExhausted);

  console.log(`\n🚀 POČETAK RADA (Brave on macOS + 360p Limit)`);
  console.log(`   📂 Liste: ${LISTS_DIR}`);
  console.log(`   🍪 Browser Source: ${BROWSER_NAME.toUpperCase()}`);
  console.log(`   🎥 Video Quality: Max 360p`);

  let round = 1;
  while (activeChannels.length > 0) {
    console.log(`\n=== KRUG ${round} (Aktivnih: ${activeChannels.length}) ===`);
    for (let i = 0
sleep function · javascript · L70-L72 (3 LOC)
generate_article_aistudio.js
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
startElapsedTimer function · javascript · L74-L86 (13 LOC)
generate_article_aistudio.js
function startElapsedTimer(prefix) {
    const start = Date.now();
    const timer = setInterval(() => {
        const elapsed = ((Date.now() - start) / 1000).toFixed(0);
        process.stderr.write(`\r      ⏱  ${prefix} ${elapsed}s...`);
    }, 1000);
    return {
        stop() {
            clearInterval(timer);
            process.stderr.write("\r" + " ".repeat(60) + "\r");
        }
    };
}
Source: Repobility analyzer · https://repobility.com
parseArgs function · javascript · L88-L120 (33 LOC)
generate_article_aistudio.js
function parseArgs() {
    const args = process.argv.slice(2);
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }

    const file = getArg("--file");
    const geminiKeyArg = getArg("--gemini-key") || process.env.GEMINI_API_KEY || null;

    if (!file) {
        console.error("❌ Obavezan argument: --file <putanja_do_srt_datoteke>");
        process.exit(1);
    }

    if (!fs.existsSync(file)) {
        console.error(`❌ Datoteka ne postoji: ${file}`);
        process.exit(1);
    }

    if (!geminiKeyArg) {
        console.error("❌ Gemini API ključ nije pronađen (--gemini-key ili GEMINI_API_KEY env)!");
        process.exit(1);
    }

    const geminiKeys = geminiKeyArg.split(",").map(k => k.trim()).filter(k => k.length > 0);
    if (geminiKeys.length === 0) {
        console.error("❌ Nevažeći format Gemini ključeva!");
        process.exit(1);
    }

    return { file, geminiKeys };
}
getArg function · javascript · L90-L93 (4 LOC)
generate_article_aistudio.js
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }
tryRepairMalformedJson function · javascript · L122-L161 (40 LOC)
generate_article_aistudio.js
function tryRepairMalformedJson(text) {
    let repaired = text;
    const MAX_FIXES = 100;

    for (let i = 0; i < MAX_FIXES; i++) {
        try {
            return JSON.parse(repaired);
        } catch (e) {
            const posMatch = e.message.match(/position (\d+)/);
            if (!posMatch) return null;
            const pos = parseInt(posMatch[1]);

            if (e.message.includes("Expected ','") && repaired[pos] === ':') {
                let k = pos - 1;
                while (k >= 0 && /\s/.test(repaired[k])) k--;
                if (k >= 0 && repaired[k] === '"') {
                    k--;
                    while (k >= 0 && repaired[k] !== '"') k--;
                    if (k >= 0) {
                        repaired = repaired.slice(0, k) + '{ ' + repaired.slice(k);
                        continue;
                    }
                }
            }

            if (e.message.includes("Expected ','") && repaired[pos] === ']') {
                repaired = repaired
extractJsonFromText function · javascript · L163-L179 (17 LOC)
generate_article_aistudio.js
function extractJsonFromText(text) {
    let clean = text.trim();
    clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
    clean = clean.trim();
    try {
        return JSON.parse(clean);
    } catch (firstErr) {
        const repaired = tryRepairMalformedJson(clean);
        if (repaired !== null) {
            console.error(`      🔧 JSON automatski popravljen (malformirani niz objekata).`);
            return repaired;
        }
        console.error(`      ⚠️  JSON parse error: ${firstErr.message}`);
        console.error(`      ⚠️  Raw response (first 500 chars): ${text.substring(0, 500)}`);
        throw firstErr;
    }
}
callGemini function · javascript · L185-L271 (87 LOC)
generate_article_aistudio.js
async function callGemini(systemPrompt, userMessage, apiKeys, label = "Gemini API poziv", rawSavePath = null) {
    const payload = {
        contents: [
            {
                role: "user",
                parts: [{ text: userMessage }]
            }
        ],
        systemInstruction: {
            role: "system",
            parts: [{ text: systemPrompt }]
        },
        generationConfig: {
            temperature: 0.2,
            responseMimeType: "application/json"
        }
    };

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
        const currentKey = apiKeys[currentKeyIndex];
        currentKeyIndex = (currentKeyIndex + 1) % apiKeys.length;
        const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent?key=${currentKey}`;

        const timer = startElapsedTimer(`${label} (AI Studio, ključ ${currentKeyIndex === 0 ? apiKeys.length : currentKeyIndex}/${apiKeys.length}, pokušaj ${attempt}/${MAX_RETRIES})`);
        try {
            const r
main function · javascript · L275-L468 (194 LOC)
generate_article_aistudio.js
async function main() {
    const { file, geminiKeys } = parseArgs();

    console.log("");
    console.log("╔══════════════════════════════════════════════════╗");
    console.log("║   📰 GEMINI ARTICLE GENERATOR — AI STUDIO       ║");
    console.log("╚══════════════════════════════════════════════════╝");
    console.log(`   📂 Datoteka: ${file}`);
    console.log(`   🤖 Model:    ${GEMINI_MODEL}`);
    console.log(`   🌐 Endpoint: AI Studio (API key, naplaćuje karticu!)`);
    console.log(`   🔑 Ključevi: ${geminiKeys.length} aktivnih API ključa/eva`);
    console.log("");

    const srtContent = fs.readFileSync(file, "utf-8");
    const baseDir = path.dirname(file);
    const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");

    const today = new Date();
    const dateStr = today.toISOString().split('T')[0];

    const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
    const articlePath = path.join(baseDir, `${basename}_${dateStr}_$
sleep function · javascript · L93-L95 (3 LOC)
generate_article_gemini.js
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
startElapsedTimer function · javascript · L98-L110 (13 LOC)
generate_article_gemini.js
function startElapsedTimer(prefix) {
    const start = Date.now();
    const timer = setInterval(() => {
        const elapsed = ((Date.now() - start) / 1000).toFixed(0);
        process.stderr.write(`\r      ⏱  ${prefix} ${elapsed}s...`);
    }, 1000);
    return {
        stop() {
            clearInterval(timer);
            process.stderr.write("\r" + " ".repeat(60) + "\r"); // očisti liniju
        }
    };
}
All rows scored by the Repobility analyzer (https://repobility.com)
getAccessToken function · javascript · L113-L120 (8 LOC)
generate_article_gemini.js
function getAccessToken() {
    try {
        return execSync("gcloud auth print-access-token", { encoding: "utf-8" }).trim();
    } catch (err) {
        console.error("❌ Ne mogu dohvatiti access token. Pokreni: gcloud auth login");
        process.exit(1);
    }
}
parseArgs function · javascript · L122-L160 (39 LOC)
generate_article_gemini.js
function parseArgs() {
    const args = process.argv.slice(2);
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }

    const file = getArg("--file");
    const inputDir = getArg("--input-dir");
    const channel = getArg("--channel");
    const limit = getArg("--limit") ? parseInt(getArg("--limit"), 10) : null;
    const dryRun = args.includes("--dry-run");

    if (!file && !inputDir) {
        console.error("❌ Obavezan argument: --file <putanja> ili --input-dir <putanja>");
        console.error("");
        console.error("Primjeri:");
        console.error("  node generate_article_gemini.js --file /path/to/transcript.srt");
        console.error("  node generate_article_gemini.js --input-dir /Volumes/DOMOVINA1TB/fetch_domovina_tv_output");
        console.error("  node generate_article_gemini.js --input-dir ... --channel domovina_tv --limit 10");
        console.error("  node generate_a
getArg function · javascript · L124-L127 (4 LOC)
generate_article_gemini.js
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }
tryRepairMalformedJson function · javascript · L164-L208 (45 LOC)
generate_article_gemini.js
function tryRepairMalformedJson(text) {
    let repaired = text;
    const MAX_FIXES = 100;

    for (let i = 0; i < MAX_FIXES; i++) {
        try {
            return JSON.parse(repaired);
        } catch (e) {
            const posMatch = e.message.match(/position (\d+)/);
            if (!posMatch) return null;
            const pos = parseInt(posMatch[1]);

            // Slučaj 1: Očekivano ',' ili ']' a na poziciji je ':'
            // → goli key-value par u nizu, nedostaje '{' oko objekta
            if (e.message.includes("Expected ','") && repaired[pos] === ':') {
                // Pronađi otvarajući " imena ključa (preskoči whitespace i zatvarajući ")
                let k = pos - 1;
                while (k >= 0 && /\s/.test(repaired[k])) k--; // preskoči razmake između ključa i :
                if (k >= 0 && repaired[k] === '"') {
                    k--; // preskoči zatvarajući "
                    while (k >= 0 && repaired[k] !== '"') k--; // pronađi otvarajući "
    
extractJsonFromText function · javascript · L211-L229 (19 LOC)
generate_article_gemini.js
function extractJsonFromText(text) {
    let clean = text.trim();
    // Ukloni sve markdown code block wrappere (```json, ```, ili samo ```)
    clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
    clean = clean.trim();
    try {
        return JSON.parse(clean);
    } catch (firstErr) {
        // Pokušaj automatski popraviti česte Gemini malformacije
        const repaired = tryRepairMalformedJson(clean);
        if (repaired !== null) {
            console.error(`      🔧 JSON automatski popravljen (malformirani niz objekata).`);
            return repaired;
        }
        console.error(`      ⚠️  JSON parse error: ${firstErr.message}`);
        console.error(`      ⚠️  Raw response (first 500 chars): ${text.substring(0, 500)}`);
        throw firstErr;
    }
}
saveBlockedMarker function · javascript · L239-L252 (14 LOC)
generate_article_gemini.js
function saveBlockedMarker(srtPath, err) {
    const baseDir = path.dirname(srtPath);
    const base = path.basename(srtPath).replace(/\.srt$/, "");
    const blockedPath = path.join(baseDir, `${base}.blocked.json`);
    const blockedData = {
        blocked_at: new Date().toISOString(),
        model: GEMINI_MODEL,
        reason: err.blockReason,
        source_file: path.basename(srtPath),
        raw_response: err.rawResponse
    };
    fs.writeFileSync(blockedPath, JSON.stringify(blockedData, null, 2), "utf-8");
    return blockedPath;
}
hasCompleteArticle function · javascript · L258-L282 (25 LOC)
generate_article_gemini.js
function hasCompleteArticle(channelDir, srtFilename) {
    const basename = srtFilename.replace(/\.(srt|txt)$/i, "");
    const today = new Date().toISOString().split('T')[0];
    const articlePath = path.join(channelDir, `${basename}_${today}_${GEMINI_MODEL}.article.json`);

    if (!fs.existsSync(articlePath)) return false;

    try {
        const article = JSON.parse(fs.readFileSync(articlePath, "utf-8"));
        if (!article.iterations || article.iterations.length === 0) return false;

        // Provjeri postoji li outline za usporedbu broja iteracija
        const outlinePath = path.join(channelDir, `${basename}_${today}_${GEMINI_MODEL}.outline.json`);
        if (fs.existsSync(outlinePath)) {
            const outline = JSON.parse(fs.readFileSync(outlinePath, "utf-8"));
            const expectedCount = Array.isArray(outline) ? outline.length : (outline.iterations?.length || 0);
            if (article.iterations.length < expectedCount) return false;
        }

        // Sve 
discoverPendingFiles function · javascript · L290-L326 (37 LOC)
generate_article_gemini.js
function discoverPendingFiles(inputDir, channelFilter) {
    const byChannel = new Map();

    const entries = fs.readdirSync(inputDir, { withFileTypes: true });
    for (const entry of entries) {
        if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
        if (channelFilter && entry.name !== channelFilter) continue;

        const channelName = entry.name;
        const channelDir = path.join(inputDir, channelName);
        const pending = [];

        const files = fs.readdirSync(channelDir);
        for (const file of files) {
            if (!file.endsWith(DIARIZED_SRT_SUFFIX)) continue;
            if (file.startsWith("._")) continue;

            // Provjeri postoji li blocked marker (PROHIBITED_CONTENT itd.)
            const base = file.replace(/\.srt$/, "");
            const blockedPath = path.join(channelDir, `${base}.blocked.json`);
            if (fs.existsSync(blockedPath)) continue;

            if (!hasCompleteArticle(channelDir, file)) {
          
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
buildRoundRobinQueue function · javascript · L335-L354 (20 LOC)
generate_article_gemini.js
function buildRoundRobinQueue(byChannel) {
    const queue = [];
    const channels = [...byChannel.keys()].sort();
    let hasMore = true;
    let roundIndex = 0;

    while (hasMore) {
        hasMore = false;
        for (const ch of channels) {
            const files = byChannel.get(ch);
            if (roundIndex < files.length) {
                queue.push({ srtPath: files[roundIndex], channel: ch });
                hasMore = true;
            }
        }
        roundIndex++;
    }

    return queue;
}
getOrRefreshAccessToken function · javascript · L362-L370 (9 LOC)
generate_article_gemini.js
function getOrRefreshAccessToken() {
    const now = Date.now();
    if (cachedAccessToken && now < tokenExpiry) {
        return cachedAccessToken;
    }
    cachedAccessToken = getAccessToken();
    tokenExpiry = now + 50 * 60 * 1000;
    return cachedAccessToken;
}
callGemini function · javascript · L372-L480 (109 LOC)
generate_article_gemini.js
async function callGemini(systemPrompt, userMessage, label = "Gemini API poziv", rawSavePath = null) {
    const payload = {
        contents: [
            {
                role: "user",
                parts: [{ text: userMessage }]
            }
        ],
        systemInstruction: {
            role: "system",
            parts: [{ text: systemPrompt }]
        },
        generationConfig: {
            temperature: 0.2,
            responseMimeType: "application/json"
        }
    };

    const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent`;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
        const token = getOrRefreshAccessToken();

        const timer = startElapsedTimer(`${label} (Vertex AI, pokušaj ${attempt}/${MAX_RETRIES})`);
        try {
            const response = await fetch(url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bea
processFile function · javascript · L489-L686 (198 LOC)
generate_article_gemini.js
async function processFile(file, { exitOnError = true } = {}) {
    const srtContent = fs.readFileSync(file, "utf-8");
    const baseDir = path.dirname(file);
    const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");

    const today = new Date();
    const dateStr = today.toISOString().split('T')[0];

    const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
    const articlePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.article.json`);
    const rawDir = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}_raw`);
    if (!fs.existsSync(rawDir)) fs.mkdirSync(rawDir, { recursive: true });

    // --- FAZA 1: OUTLINE ---
    let outlineJson = null;

    if (fs.existsSync(outlinePath)) {
        console.log(`   ✅ [FAZA 1] Pronađen postojeći outline: ${path.basename(outlinePath)}`);
        outlineJson = JSON.parse(fs.readFileSync(outlinePath, "utf-8"));
    } else {
        const rawPath1 = path.join(rawDir, "
main function · javascript · L690-L772 (83 LOC)
generate_article_gemini.js
async function main() {
    const opts = parseArgs();

    console.log("");
    console.log("╔══════════════════════════════════════════════════╗");
    console.log("║   📰 GEMINI ARTICLE GENERATOR (2 FAZE)          ║");
    console.log("╚══════════════════════════════════════════════════╝");
    console.log(`   🤖 Model:    ${GEMINI_MODEL}`);
    console.log(`   🌐 Vertex AI: ${VERTEX_PROJECT} (${VERTEX_REGION})`);

    // ── Način 1: Pojedinačna datoteka ──
    if (opts.mode === "single") {
        console.log(`   📂 Datoteka: ${opts.file}`);
        console.log("");
        await processFile(opts.file, { exitOnError: true });
        console.log("");
        return;
    }

    // ── Način 2: Batch s round-robin rasporedom ──
    const { inputDir, channel, limit, dryRun } = opts;
    console.log(`   📂 Input:    ${inputDir}`);
    if (channel) console.log(`   🎯 Kanal:    ${channel}`);
    if (limit) console.log(`   🔢 Limit:    ${limit}`);
    if (dryRun) console.log("   ⚠️  DRY RUN — samo
sleep function · javascript · L68-L70 (3 LOC)
generate_article_vertexai_express.js
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
startElapsedTimer function · javascript · L72-L84 (13 LOC)
generate_article_vertexai_express.js
function startElapsedTimer(prefix) {
    const start = Date.now();
    const timer = setInterval(() => {
        const elapsed = ((Date.now() - start) / 1000).toFixed(0);
        process.stderr.write(`\r      ⏱  ${prefix} ${elapsed}s...`);
    }, 1000);
    return {
        stop() {
            clearInterval(timer);
            process.stderr.write("\r" + " ".repeat(60) + "\r");
        }
    };
}
parseArgs function · javascript · L86-L123 (38 LOC)
generate_article_vertexai_express.js
function parseArgs() {
    const args = process.argv.slice(2);
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }

    const file = getArg("--file");
    const geminiKeyArg = getArg("--gemini-key") || process.env.GEMINI_API_KEY || null;

    if (!file) {
        console.error("❌ Obavezan argument: --file <putanja_do_srt_datoteke>");
        process.exit(1);
    }

    if (!fs.existsSync(file)) {
        console.error(`❌ Datoteka ne postoji: ${file}`);
        process.exit(1);
    }

    if (!geminiKeyArg) {
        console.error("❌ Gemini API ključ nije pronađen (--gemini-key ili GEMINI_API_KEY env)!");
        process.exit(1);
    }

    const geminiKeys = geminiKeyArg.split(",").map(k => k.trim()).filter(k => k.length > 0);
    if (geminiKeys.length === 0) {
        console.error("❌ Nevažeći format Gemini ključeva!");
        process.exit(1);
    }

    const model = getArg("--model");
 
Repobility — the code-quality scanner for AI-generated software · https://repobility.com
getArg function · javascript · L88-L91 (4 LOC)
generate_article_vertexai_express.js
    function getArg(name) {
        const idx = args.indexOf(name);
        return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
    }
tryRepairMalformedJson function · javascript · L125-L164 (40 LOC)
generate_article_vertexai_express.js
function tryRepairMalformedJson(text) {
    let repaired = text;
    const MAX_FIXES = 100;

    for (let i = 0; i < MAX_FIXES; i++) {
        try {
            return JSON.parse(repaired);
        } catch (e) {
            const posMatch = e.message.match(/position (\d+)/);
            if (!posMatch) return null;
            const pos = parseInt(posMatch[1]);

            if (e.message.includes("Expected ','") && repaired[pos] === ':') {
                let k = pos - 1;
                while (k >= 0 && /\s/.test(repaired[k])) k--;
                if (k >= 0 && repaired[k] === '"') {
                    k--;
                    while (k >= 0 && repaired[k] !== '"') k--;
                    if (k >= 0) {
                        repaired = repaired.slice(0, k) + '{ ' + repaired.slice(k);
                        continue;
                    }
                }
            }

            if (e.message.includes("Expected ','") && repaired[pos] === ']') {
                repaired = repaired
extractJsonFromText function · javascript · L166-L182 (17 LOC)
generate_article_vertexai_express.js
function extractJsonFromText(text) {
    let clean = text.trim();
    clean = clean.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
    clean = clean.trim();
    try {
        return JSON.parse(clean);
    } catch (firstErr) {
        const repaired = tryRepairMalformedJson(clean);
        if (repaired !== null) {
            console.error(`      🔧 JSON automatski popravljen (malformirani niz objekata).`);
            return repaired;
        }
        console.error(`      ⚠️  JSON parse error: ${firstErr.message}`);
        console.error(`      ⚠️  Raw response (first 500 chars): ${text.substring(0, 500)}`);
        throw firstErr;
    }
}
callGemini function · javascript · L188-L274 (87 LOC)
generate_article_vertexai_express.js
async function callGemini(systemPrompt, userMessage, apiKeys, label = "Gemini API poziv", rawSavePath = null) {
    const payload = {
        contents: [
            {
                role: "user",
                parts: [{ text: userMessage }]
            }
        ],
        systemInstruction: {
            role: "system",
            parts: [{ text: systemPrompt }]
        },
        generationConfig: {
            temperature: 0.2,
            responseMimeType: "application/json"
        }
    };

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
        const currentKey = apiKeys[currentKeyIndex];
        currentKeyIndex = (currentKeyIndex + 1) % apiKeys.length;
        const url = `${GEMINI_API_BASE}/${GEMINI_MODEL}:generateContent?key=${currentKey}`;

        const timer = startElapsedTimer(`${label} (Vertex Express, ključ ${currentKeyIndex === 0 ? apiKeys.length : currentKeyIndex}/${apiKeys.length}, pokušaj ${attempt}/${MAX_RETRIES})`);
        try {
            co
main function · javascript · L278-L471 (194 LOC)
generate_article_vertexai_express.js
async function main() {
    const { file, geminiKeys } = parseArgs();

    console.log("");
    console.log("╔══════════════════════════════════════════════════╗");
    console.log("║   📰 GEMINI ARTICLE GENERATOR — VERTEX EXPRESS   ║");
    console.log("╚══════════════════════════════════════════════════╝");
    console.log(`   📂 Datoteka: ${file}`);
    console.log(`   🤖 Model:    ${GEMINI_MODEL}`);
    console.log(`   🌐 Endpoint: Vertex AI Express (API key)`);
    console.log(`   🔑 Ključevi: ${geminiKeys.length} aktivnih API ključa/eva`);
    console.log("");

    const srtContent = fs.readFileSync(file, "utf-8");
    const baseDir = path.dirname(file);
    const basename = path.basename(file).replace(/\.(srt|txt)$/i, "");

    const today = new Date();
    const dateStr = today.toISOString().split('T')[0];

    const outlinePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MODEL}.outline.json`);
    const articlePath = path.join(baseDir, `${basename}_${dateStr}_${GEMINI_MOD
sanitizeDescription function · javascript · L53-L64 (12 LOC)
generate_whisper_prompt.js
function sanitizeDescription(str) {
    if (!str) return "nepoznat_naslov";
    str = str.toLowerCase();
    const map = {
        'č': 'c', 'ć': 'c', 'ž': 'z', 'š': 's', 'đ': 'd',
        'Č': 'c', 'Ć': 'c', 'Ž': 'z', 'Š': 's', 'Đ': 'd'
    };
    str = str.replace(/[čćžšđČĆŽŠĐ]/g, (char) => map[char] || char);
    str = str.replace(/[^a-z0-9]/g, '_');
    str = str.replace(/_+/g, '_').replace(/^_|_$/g, '');
    return str || "nepoznat_naslov";
}
extractVideoId function · javascript · L66-L71 (6 LOC)
generate_whisper_prompt.js
function extractVideoId(url) {
    url = url.trim();
    if (!url) return null;
    const m = url.match(/(?:youtu\.be\/|v=)([a-zA-Z0-9_-]{11})/);
    return m ? m[1] : null;
}
extractDataFromLine function · javascript · L73-L90 (18 LOC)
generate_whisper_prompt.js
function extractDataFromLine(line) {
    line = line.trim();
    if (!line || line.startsWith("#")) return null;
    if (line.includes("|")) {
        const parts = line.split("|");
        const url = parts[parts.length - 1].trim();
        let title = "nepoznat_naslov";
        let date = "NA";
        if (parts.length >= 3) {
            date = parts[0].trim();
            title = parts.slice(1, parts.length - 1).join(" ").trim();
        } else if (parts.length === 2) {
            title = parts[0].trim();
        }
        return { url, title, date };
    }
    return { url: line, title: "nepoznat_naslov", date: "NA" };
}
Source: Repobility analyzer · https://repobility.com
loadState function · javascript · L92-L101 (10 LOC)
generate_whisper_prompt.js
function loadState(stateFile) {
    if (fs.existsSync(stateFile)) {
        try {
            return JSON.parse(fs.readFileSync(stateFile, "utf-8"));
        } catch (e) {
            console.error(`[GREŠKA] Neispravan JSON stanja: ${stateFile}`);
        }
    }
    return { completed: [], failed: [] };
}
callLLM function · javascript · L108-L162 (55 LOC)
generate_whisper_prompt.js
function callLLM(title, description) {
    const userMessage = `NASLOV: ${title}\n\nOPIS:\n${description}`;

    const payload = JSON.stringify({
        model: LM_STUDIO_MODEL,
        messages: [
            { role: "system", content: SYSTEM_PROMPT },
            { role: "user", content: userMessage }
        ],
        temperature: 0.0,
        max_tokens: 300
    });

    return new Promise((resolve, reject) => {
        const url = new URL(LM_STUDIO_URL);
        const options = {
            hostname: url.hostname,
            port: url.port,
            path: url.pathname,
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Content-Length": Buffer.byteLength(payload)
            }
        };

        const req = http.request(options, (res) => {
            let data = "";
            res.on("data", (chunk) => { data += chunk; });
            res.on("end", () => {
                try {
                    const js
sanitizeLLMOutput function · javascript · L167-L192 (26 LOC)
generate_whisper_prompt.js
function sanitizeLLMOutput(raw) {
    // Makni sve što nije dio ključnih riječi (LLM ponekad doda uvodni tekst)
    let cleaned = raw.trim();

    // Ako ima više linija, uzmi samo prvu koja izgleda kao lista ključnih riječi
    const lines = cleaned.split('\n').map(l => l.trim()).filter(l => l.length > 0);
    for (const line of lines) {
        // Linija koja sadrži zareze i nema razmaka nakon zareza izgleda kao ispravan output
        if (line.includes(',') && !line.includes(': ')) {
            cleaned = line;
            break;
        }
    }

    // Zamijeni eventualne razmake nakon zareza
    cleaned = cleaned.replace(/,\s+/g, ',');

    // Zamijeni eventualne razmake unutar pojmova podcrtom
    // (LLM ponekad zaboravi pravilo o podcrti)
    const parts = cleaned.split(',');
    const fixed = parts
        .map(p => p.trim().replace(/\s+/g, '_'))
        .filter(p => p.length >= 2);

    return fixed.join(',');
}
‹ prevpage 2 / 4next ›