Pages

Se afișează postările cu eticheta open source. Afișați toate postările
Se afișează postările cu eticheta open source. Afișați toate postările

marți, 15 septembrie 2026

Google Apps Script : show tags videos from youtube playlist.

Today marks the first part of managing tags for uploaded YouTube videos. Assigning the right tags to every video is challenging and time-consuming; plus, if you rush, you might lose track of what you’ve uploaded. This Google Apps Script displays the tags for the videos in a playlist. While it is possible to use a script to automatically check and modify them, that process is more complex. Here is the initial, simple script for my main playlist on the @catafest YouTube channel.
function showAllVideoTagsFromPlaylist() {
  const playlistId = "PLIDHlEkMih2OssDFquu0iWSNOTsFeuk8v";
  let pageToken = null;
  do {
    const playlistResponse =
      YouTube.PlaylistItems.list(
        "snippet",
        {
          playlistId: playlistId,
          maxResults: 50,
          pageToken: pageToken
        }
      );
    playlistResponse.items.forEach(item => {
      const videoId =
        item.snippet.resourceId.videoId;
      const videoResponse =
        YouTube.Videos.list(
          "snippet",
          {
            id: videoId
          }
        );
      if (!videoResponse.items.length) {
        return;
      }
      const video =
        videoResponse.items[0];
      const tags =
        video.snippet.tags || [];
      Logger.log("====================================");
      Logger.log("TITLE: " + video.snippet.title);
      Logger.log("VIDEO ID: " + videoId);
      Logger.log("TAGS: " + tags.join(", "));
    });
    pageToken = playlistResponse.nextPageToken;
  } while (pageToken);
}

marți, 7 iulie 2026

Google Apps Script : example of transition from a Google Sheet to a formatted Google Doc.

This script that automates the transition from a Google Sheet to a formatted Google Doc. Here is a breakdown of what this solution does:
Custom Menu Integration: The script adds a Custom Menu directly into your Google Sheet interface. With a single click, you can trigger processes like fetching data or generating reports, making the tool incredibly easy to use without touching the code.
This example is based on this URL : https://www.planetpython.org/opml.xml.
Intelligent Data Processing: It doesn't just copy text; it parses RSS and Atom feeds, extracts titles and links, and prepares them for your document. It intelligently handles the HYPERLINK formulas you use in Sheets, translating them into real, clickable links in your final document.
Automatic Folder Management: One of the most useful features of this script is its ability to keep your Drive organized. Instead of cluttering your root directory, the script automatically identifies the folder where your source spreadsheet is located and creates the new document right there.
Built-in Troubleshooting: I have included logging functionality throughout the code. If a link is malformed or a feed fails to load, the script logs exactly what happened, allowing you to debug issues quickly via the Apps Script Dashboard.
Let's see the source code for Google Apps Scripts:
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Custom Menu')
    .addItem('Read OPML PlanetPython', 'fetchAndProcessFeed')
    .addItem('Download Selected Content', 'downloadSelectedContentWrapper')
    .addItem('Send Sheet via Email', 'sendSheetViaEmail')
    .addToUi();
}

function fetchAndProcessFeed() {
  const dateTime = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd_HH-mm");
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = spreadsheet.getSheetByName(dateTime);
  
  if (!sheet) {
    sheet = spreadsheet.insertSheet(dateTime);
  } else {
    sheet.clear();
  }
  
  sheet.appendRow(["Date", "Source", "Title", "Select"]);
  
  const opmlUrl = "https://www.planetpython.org/opml.xml";
  const opmlContent = UrlFetchApp.fetch(opmlUrl).getContentText();
  const opmlDoc = XmlService.parse(opmlContent);
  const outlines = opmlDoc.getRootElement().getChild("body").getChildren("outline");
  
  outlines.forEach(outline => {
    const text = outline.getAttribute("text")?.getValue().replace(/"/g, '""').replace(/[<>&]/g, '') || "";
    const xmlUrl = outline.getAttribute("xmlUrl")?.getValue() || "";
    
    try {
      const feedContent = UrlFetchApp.fetch(xmlUrl).getContentText();
      const feedDoc = XmlService.parse(feedContent);
      const root = feedDoc.getRootElement();
      
      if (root.getName() === "rss") {
        processRssFeed(root, text, xmlUrl, sheet);
      } else if (root.getName() === "feed") {
        processAtomFeed(root, text, xmlUrl, sheet);
      }
    } catch (e) {
      Logger.log(`Error processing feed ${text} (${xmlUrl}): ${e}`);
    }
  });
}

function processRssFeed(root, source, xmlUrl, sheet) {
  const items = root.getChild("channel")?.getChildren("item") || [];
  items.forEach(item => {
    const title = item.getChild("title")?.getValue().replace(/"/g, '""').replace(/[<>&]/g, '') || "";
    const pubDate = item.getChild("pubDate")?.getValue() || "";
    const link = item.getChild("link")?.getValue().replace(/[<>&]/g, '') || xmlUrl;
    sheet.appendRow([
      pubDate,
      `=HYPERLINK("${xmlUrl}", "${source}")`,
      `=HYPERLINK("${link}", "${title}")`,
      ""
    ]);
  });
}

function processAtomFeed(root, source, xmlUrl, sheet) {
  const entries = root.getChildren("entry") || [];
  entries.forEach(entry => {
    const title = entry.getChild("title")?.getValue().replace(/"/g, '""').replace(/[<>&]/g, '') || "";
    const updated = entry.getChild("updated")?.getValue() || "";
    const link = entry.getChild("link")?.getAttribute("href")?.getValue().replace(/[<>&]/g, '') || xmlUrl;
    sheet.appendRow([
      updated,
      `=HYPERLINK("${xmlUrl}", "${source}")`,
      `=HYPERLINK("${link}", "${title}")`,
      ""
    ]);
  });
}

function downloadSelectedContentWrapper() {
  downloadSelectedContent();
}

async function downloadSelectedContent() {
  const dateTime = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd_HH-mm");
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getActiveSheet();
  const data = sheet.getDataRange().getValues();
  const doc = DocumentApp.create(`Content_${dateTime}`);
  const folderId = DriveApp.getFileById(spreadsheet.getId()).getParents().next().getId();
  DriveApp.getFileById(doc.getId()).moveTo(DriveApp.getFolderById(folderId));
  
  let contentAdded = false;
  const body = doc.getBody();
  
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    if (row[3] == 1) {
      const sourceMatch = row[1].toString().match(/^=HYPERLINK\("([^"]+)",\s*"([^"]+)"\)$/);
      const source = sourceMatch ? sourceMatch[2] : row[1].toString().replace(/[<>&]/g, '') || "Unknown source";
      const titleMatch = row[2].toString().match(/^=HYPERLINK\("([^"]+)",\s*"([^"]+)"\)$/);
      const title = titleMatch ? titleMatch[2] : row[2].toString().replace(/[<>&]/g, '');
      const link = titleMatch ? titleMatch[1] : "";
      
      Logger.log(`Processing ${title} with link: ${link}`);
      
      try {
        body.appendParagraph(source).setHeading(DocumentApp.ParagraphHeading.HEADING1);
        const titleParagraph = body.appendParagraph(title).setHeading(DocumentApp.ParagraphHeading.HEADING2);
        if (link) titleParagraph.setLinkUrl(link); // Titlu ca hyperlink
        body.appendParagraph(""); // Spațiu între intrări
        contentAdded = true;
        Logger.log(`Added ${title} with link`);
      } catch (e) {
        Logger.log(`Error processing ${title} (${link}): ${e}`);
        body.appendParagraph(source).setHeading(DocumentApp.ParagraphHeading.HEADING1);
        const titleParagraph = body.appendParagraph(title).setHeading(DocumentApp.ParagraphHeading.HEADING2);
        if (link) titleParagraph.setLinkUrl(link);
        body.appendParagraph("");
        contentAdded = true;
      }
    }
  }
  
  if (!contentAdded) {
    Logger.log("No content was added to the document.");
    body.appendParagraph("No content was selected or available.");
  }
}


function sendSheetViaEmail() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getActiveSheet();
  const sheetName = sheet.getName();
  const data = sheet.getDataRange().getValues();
  
  let htmlContent = `
    <html>
      <head>
        <title>${sheetName}</title>
      </head>
      <body>
        <h1>${sheetName}</h1>
        <table border="1">
          <tr>
            <th>Date</th>
            <th>Source</th>
            <th>Title</th>
            <th>Select</th>
          </tr>
  `;
  
  data.forEach((row, index) => {
    if (index > 0) {
      const date = row[0] || "";
      const sourceMatch = row[1].toString().match(/^=HYPERLINK\("([^"]+)",\s*"([^"]+)"\)$/);
      const sourceUrl = sourceMatch ? sourceMatch[1] : "";
      const sourceText = sourceMatch ? sourceMatch[2] : row[1].toString().replace(/[<>&]/g, '');
      const titleMatch = row[2].toString().match(/^=HYPERLINK\("([^"]+)",\s*"([^"]+)"\)$/);
      const titleUrl = titleMatch ? titleMatch[1] : "";
      const titleText = titleMatch ? titleMatch[2] : row[2].toString().replace(/[<>&]/g, '');
      const select = row[3] || "";
      htmlContent += `
        <tr>
          <td>${date}</td>
          <td><a href="${sourceUrl}">${sourceText}</a></td>
          <td><a href="${titleUrl}">${titleText}</a></td>
          <td>${select}</td>
        </tr>
      `;
    }
  });
  
  htmlContent += `
        </table>
      </body>
    </html>
  `;
  
  const mimetype = "application/epub+zip";
  const containerXml = `
    <?xml version="1.0"?>
    <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
      <rootfiles>
        <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
      </rootfiles>
    </container>
  `;
  
  const contentOpf = `
    <?xml version="1.0" encoding="UTF-8"?>
    <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" version="2.0">
      <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
        <dc:title>${sheetName}</dc:title>
        <dc:identifier id="bookid">urn:uuid:${Utilities.getUuid()}</dc:identifier>
        <dc:language>en</dc:language>
      </metadata>
      <manifest>
        <item id="content" href="content.html" media-type="application/xhtml+xml"/>
      </manifest>
      <spine>
        <itemref idref="content"/>
      </spine>
    </package>
  `;
  
  const blobs = [
    Utilities.newBlob("mimetype", "text/plain", "mimetype").setContentType(mimetype),
    Utilities.newBlob(containerXml, "application/xml", "META-INF/container.xml"),
    Utilities.newBlob(contentOpf, "application/xml", "OEBPS/content.opf"),
    Utilities.newBlob(htmlContent, "application/xhtml+xml", "OEBPS/content.html")
  ];
  
  const zipOutput = Utilities.zip(blobs, `Sheet_${sheetName}.epub`);
  
  MailApp.sendEmail({
    to: "catalinfest@gmail.com,catafest@yahoo.com",
    subject: `Sheet activ: ${sheetName}`,
    body: "Vezi atașat sheet-ul activ în format EPUB.",
    attachments: [zipOutput]
  });
}

marți, 16 iunie 2026

Tools : a simple PowerShell script for UEFI, VeraCrypt, and more security information.

UEFI Secure Boot keys, used to sign the first stage boot loader, are expiring in June 2026
First, let's see this information that could highlight the intrusion capabilities of a hacking attack on an information system in time and space:
1. Secure Boot, even with old keys – protects BEFORE Windows starts
Secure Boot protects against:
  • bootkits
  • UEFI rootkits
  • bootloader tampering
  • malware that injects itself before Windows loads
It is a hardware + firmware protection, enforced by UEFI.
Even if your keys are old, Secure Boot is still:
  • much safer than having Secure Boot disabled
  • a firmware‑level protection
  • impossible to bypass without physical access + complex attacks
Old keys do not mean “insecure”; it only means Microsoft will replace them in the future.
2. VeraCrypt System Encryption – protects AFTER the bootloader starts
VeraCrypt protects:
  • the data on your disk
  • the confidentiality of your files
  • access to your system if someone steals your laptop
But it does NOT protect against:
  • bootkits
  • UEFI rootkits
  • bootloader tampering
  • firmware‑level attacks
Because VeraCrypt:
  • replaces the Windows bootloader
  • disables Secure Boot
  • is not cryptographically signed for UEFI
  • does not provide protection against pre‑boot attacks
One basic script created by copilot to show some info:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object System.Windows.Forms.Form
$form.Text = "UEFI Bootloader Detector"
$form.Size = New-Object System.Drawing.Size(800,600)
$form.StartPosition = "CenterScreen"

$box = New-Object System.Windows.Forms.TextBox
$box.Multiline = $true
$box.ScrollBars = "Vertical"
$box.ReadOnly = $true
$box.Font = New-Object System.Drawing.Font("Consolas",10)
$box.Dock = "Fill"
$form.Controls.Add($box)

function Add-Line($text) {
    $box.AppendText($text + "`r`n")
}

Add-Line "=== UEFI Bootloader Detector ==="
Add-Line ""

# Montăm partiția EFI
mountvol S: /s | Out-Null

Add-Line "EFI Partition Contents:"
$efi = Get-ChildItem S:\EFI -ErrorAction SilentlyContinue
foreach ($item in $efi) {
    Add-Line "  $($item.Name)"
}

Add-Line ""
Add-Line "=== Bootloader Detection ==="

# Windows Boot Manager
Add-Line ""
Add-Line "Windows Boot Manager:"
if (Test-Path "S:\EFI\Microsoft\Boot\bootmgfw.efi") {
    Add-Line "  ✔ Windows bootloader detected"
} else {
    Add-Line "  ✖ Windows bootloader NOT found"
}

# VeraCrypt
Add-Line ""
Add-Line "VeraCrypt:"
if (Test-Path "S:\EFI\VeraCrypt\DcsBoot.efi") {
    Add-Line "  ✔ VeraCrypt bootloader detected"
} else {
    Add-Line "  ✖ VeraCrypt bootloader NOT found"
}

# GRUB
Add-Line ""
Add-Line "GRUB:"
$grubPaths = @(
    "S:\EFI\ubuntu\grubx64.efi",
    "S:\EFI\fedora\grubx64.efi",
    "S:\EFI\debian\grubx64.efi",
    "S:\EFI\opensuse\grubx64.efi",
    "S:\EFI\centos\grubx64.efi"
)

$grubFound = $false
foreach ($path in $grubPaths) {
    if (Test-Path $path) {
        Add-Line "  ✔ GRUB detected at $path"
        $grubFound = $true
    }
}
if (-not $grubFound) {
    Add-Line "  ✖ GRUB not found"
}

# rEFInd
Add-Line ""
Add-Line "rEFInd:"
if (Test-Path "S:\EFI\refind\refind_x64.efi") {
    Add-Line "  ✔ rEFInd detected"
} else {
    Add-Line "  ✖ rEFInd not found"
}

# systemd-boot
Add-Line ""
Add-Line "systemd-boot:"
if (Test-Path "S:\EFI\systemd\systemd-bootx64.efi") {
    Add-Line "  ✔ systemd-boot detected"
} else {
    Add-Line "  ✖ systemd-boot not found"
}

# Fallback EFI
Add-Line ""
Add-Line "Fallback Bootloader:"
if (Test-Path "S:\EFI\Boot\bootx64.efi") {
    Add-Line "  ✔ Fallback bootloader detected (bootx64.efi)"
} else {
    Add-Line "  ✖ Fallback bootloader not found"
}

Add-Line ""
Add-Line "=== Detection Complete ==="

$form.ShowDialog()

luni, 20 aprilie 2026

Tools : two script in powershell for audit authentication ...

I used copilot to create these powershell scripts to check this windows 10 operating system, because not work well.I think is a hacking with admnistrator access over ehernet.
Purpose : Collect Kerberos and NTLM authentication events from the Windows Security Log and export them into a JSON file for SIEM ingestion.
What it does:
Reads key authentication events (4768, 4769, 4771, 4624, 4625, 4776).
Extracts useful fields (user, IP, ticket type, failure reason, timestamp).
Converts everything into structured JSON.
Saves the JSON file so Splunk, Sentinel, ELK, Wazuh, or Graylog can ingest it.
Does not perform analysis — it only collects and exports raw data.
# ============================
# AUDIT AUTENTIFICARI KERBEROS + NTLM
# Compatibil: AD, SIEM, WEF
# ============================

$OutputFile = "C:\Logs\Kerberos_Audit_$(Get-Date -Format yyyyMMdd_HHmmss).json"

# Evenimente relevante
$EventIDs = @(4768, 4769, 4771, 4624, 4625, 4776)

# Preluare evenimente din Security Log
$Events = Get-WinEvent -FilterHashtable @{
    LogName = "Security"
    Id      = $EventIDs
} -ErrorAction SilentlyContinue

# Parsare evenimente
$Parsed = foreach ($ev in $Events) {

    $xml = [xml]$ev.ToXml()
    $data = $xml.Event.EventData.Data

    [PSCustomObject]@{
        TimeCreated     = $ev.TimeCreated
        EventID         = $ev.Id
        Machine         = $ev.MachineName
        User            = $data[1].'#text'
        IP              = $data[18].'#text'
        TicketType      = switch ($ev.Id) {
                            4768 { "TGT Request" }
                            4769 { "Service Ticket (TGS)" }
                            4771 { "Kerberos Failure" }
                            4776 { "NTLM Authentication" }
                            4624 { "Logon Success" }
                            4625 { "Logon Failure" }
                            default { "Unknown" }
                          }
        Status          = $data[2].'#text'
        ServiceName     = $data[3].'#text'
        FailureReason   = $data[5].'#text'
        RawMessage      = $ev.Message
    }
}

# Export JSON pentru SIEM
$Parsed | ConvertTo-Json -Depth 5 | Out-File $OutputFile -Encoding UTF8

Write-Host "Audit complet. Log salvat în: $OutputFile"
The advanced script : alerts + dashboards + TXT report for detect suspicious authentication behavior and generate human-readable alerts.
It analyzes the events and identifies:
Brute-force attacks
Too many failures from the same user or IP in a short time window.
NTLM fallback
Detects when authentication falls back from Kerberos to NTLM (Event 4776).
Useful for spotting misconfigurations or downgrade attacks.
Kerberos failures
Detects repeated 4771 errors (bad passwords, clock skew, SPN issues).
# ============================
# AUDIT AUTENTIFICARI KERBEROS + NTLM
# DETECTIE: BRUTEFORCE, NTLM FALLBACK, KERBEROS FAILURES
# ============================

$LogFolder = "C:\Logs"
if (!(Test-Path $LogFolder)) {
    New-Item -ItemType Directory -Path $LogFolder | Out-Null
}

$Timestamp   = Get-Date -Format yyyyMMdd_HHmmss
$JsonFile    = "$LogFolder\Kerberos_Audit_$Timestamp.json"
$ReportFile  = "$LogFolder\Kerberos_Alerts_$Timestamp.txt"

# Interval analiză (ex: ultimele 2 ore)
$HoursBack = 2
$StartTime = (Get-Date).AddHours(-$HoursBack)

# Praguri detecție
$BruteForceThreshold = 5   # minim X eșecuri
$BruteForceWindowMin = 10  # în Y minute

$EventIDs = @(4768, 4769, 4771, 4624, 4625, 4776)

$Events = Get-WinEvent -FilterHashtable @{
    LogName   = "Security"
    Id        = $EventIDs
    StartTime = $StartTime
} -ErrorAction SilentlyContinue

$Parsed = foreach ($ev in $Events) {
    $xml  = [xml]$ev.ToXml()
    $data = $xml.Event.EventData.Data

    [PSCustomObject]@{
        TimeCreated   = $ev.TimeCreated
        EventID       = $ev.Id
        Machine       = $ev.MachineName
        User          = $data[1].'#text'
        IP            = $data[18].'#text'
        TicketType    = switch ($ev.Id) {
                            4768 { "TGT Request" }
                            4769 { "Service Ticket (TGS)" }
                            4771 { "Kerberos Failure" }
                            4776 { "NTLM Authentication" }
                            4624 { "Logon Success" }
                            4625 { "Logon Failure" }
                            default { "Unknown" }
                        }
        Status        = $data[2].'#text'
        ServiceName   = $data[3].'#text'
        FailureReason = $data[5].'#text'
        RawMessage    = $ev.Message
    }
}

# Export JSON brut pentru SIEM
$Parsed | ConvertTo-Json -Depth 5 | Out-File $JsonFile -Encoding UTF8

# ============================
# DETECTIE: NTLM FALLBACK
# ============================

$NtlmEvents = $Parsed | Where-Object { $_.EventID -eq 4776 }
$NtlmCount  = $NtlmEvents.Count

# ============================
# DETECTIE: KERBEROS FAILURES
# ============================

$KerbFailEvents = $Parsed | Where-Object { $_.EventID -eq 4771 }
$KerbFailCount  = $KerbFailEvents.Count

# ============================
# DETECTIE: BRUTE FORCE (USER / IP)
# ============================

$FailureEvents = $Parsed | Where-Object { $_.EventID -in 4625, 4771, 4776 }

$BruteForceAlerts = @()

# Grupare pe User
$FailureEvents | Group-Object User | ForEach-Object {
    $user = $_.Name
    if ([string]::IsNullOrWhiteSpace($user)) { return }

    $events = $_.Group | Sort-Object TimeCreated
    for ($i = 0; $i -lt $events.Count; $i++) {
        $startTime = $events[$i].TimeCreated
        $windowEnd = $startTime.AddMinutes($BruteForceWindowMin)
        $windowEvents = $events | Where-Object { $_.TimeCreated -ge $startTime -and $_.TimeCreated -le $windowEnd }

        if ($windowEvents.Count -ge $BruteForceThreshold) {
            $BruteForceAlerts += [PSCustomObject]@{
                Type        = "BruteForce_User"
                User        = $user
                Count       = $windowEvents.Count
                FirstEvent  = $startTime
                LastEvent   = $windowEvents[-1].TimeCreated
            }
            break
        }
    }
}

# Grupare pe IP
$FailureEvents | Group-Object IP | ForEach-Object {
    $ip = $_.Name
    if ([string]::IsNullOrWhiteSpace($ip)) { return }

    $events = $_.Group | Sort-Object TimeCreated
    for ($i = 0; $i -lt $events.Count; $i++) {
        $startTime = $events[$i].TimeCreated
        $windowEnd = $startTime.AddMinutes($BruteForceWindowMin)
        $windowEvents = $events | Where-Object { $_.TimeCreated -ge $startTime -and $_.TimeCreated -le $windowEnd }

        if ($windowEvents.Count -ge $BruteForceThreshold) {
            $BruteForceAlerts += [PSCustomObject]@{
                Type        = "BruteForce_IP"
                IP          = $ip
                Count       = $windowEvents.Count
                FirstEvent  = $startTime
                LastEvent   = $windowEvents[-1].TimeCreated
            }
            break
        }
    }
}

# ============================
# GENERARE RAPORT TXT
# ============================

$ReportLines = @()

$ReportLines += "=== KERBEROS / NTLM AUDIT REPORT ==="
$ReportLines += "Interval analizat: ultimele $HoursBack ore"
$ReportLines += "Generat la: $(Get-Date)"
$ReportLines += ""
$ReportLines += "Total evenimente analizate: $($Parsed.Count)"
$ReportLines += "NTLM Authentication (4776): $NtlmCount"
$ReportLines += "Kerberos Failures (4771):   $KerbFailCount"
$ReportLines += ""

$ReportLines += "=== NTLM FALLBACK DETECTIE ==="
if ($NtlmCount -gt 0) {
    $ReportLines += "ATENTIE: Exista $NtlmCount evenimente NTLM (posibil fallback de la Kerberos)."
} else {
    $ReportLines += "Nu au fost detectate evenimente NTLM (4776) in interval."
}
$ReportLines += ""

$ReportLines += "=== KERBEROS FAILURES DETECTIE ==="
if ($KerbFailCount -gt 0) {
    $ReportLines += "ATENTIE: Exista $KerbFailCount esecuri Kerberos (4771)."
} else {
    $ReportLines += "Nu au fost detectate esecuri Kerberos (4771) in interval."
}
$ReportLines += ""

$ReportLines += "=== BRUTE FORCE DETECTIE ==="
if ($BruteForceAlerts.Count -gt 0) {
    foreach ($alert in $BruteForceAlerts) {
        if ($alert.Type -eq "BruteForce_User") {
            $ReportLines += "Brute force pe USER: $($alert.User) | Count: $($alert.Count) | Interval: $($alert.FirstEvent) - $($alert.LastEvent)"
        } elseif ($alert.Type -eq "BruteForce_IP") {
            $ReportLines += "Brute force pe IP:   $($alert.IP) | Count: $($alert.Count) | Interval: $($alert.FirstEvent) - $($alert.LastEvent)"
        }
    }
} else {
    $ReportLines += "Nu au fost detectate pattern-uri brute force (user/IP) peste pragul $BruteForceThreshold in $BruteForceWindowMin minute."
}
$ReportLines += ""

$ReportLines += "=== SUGESTII DASHBOARD (Splunk / Sentinel / Kibana) ==="
$ReportLines += "Splunk - Kerberos Failures:"
$ReportLines += "  index=kerberos EventID=4771 | stats count by User, IP, FailureReason"
$ReportLines += ""
$ReportLines += "Splunk - NTLM Fallback:"
$ReportLines += "  index=kerberos EventID=4776 | stats count by User, IP, Machine"
$ReportLines += ""
$ReportLines += "Splunk - Brute Force (User):"
$ReportLines += "  index=kerberos EventID=4625 OR EventID=4771 OR EventID=4776"
$ReportLines += "  | bin _time span=10m"
$ReportLines += "  | stats count by User, _time"
$ReportLines += "  | where count >= $BruteForceThreshold"
$ReportLines += ""
$ReportLines += "Kibana / Elastic:"
$ReportLines += "  Filtre pe campurile: EventID, User, IP, FailureReason, Machine"
$ReportLines += ""
$ReportLines += "Sentinel:"
$ReportLines += "  SecurityEvent"
$ReportLines += "  | where EventID in (4768, 4769, 4771, 4624, 4625, 4776)"
$ReportLines += "  | summarize count() by Account, IPAddress, EventID, bin(TimeGenerated, 10m)"
$ReportLines += ""

$ReportLines | Out-File $ReportFile -Encoding UTF8

# ============================
# AFISARE IN CONSOLA
# ============================

Write-Host "=== REZUMAT AUDIT ==="
Write-Host "JSON log:    $JsonFile"
Write-Host "Raport TXT:  $ReportFile"
Write-Host "Evenimente:  $($Parsed.Count)"
Write-Host "NTLM (4776): $NtlmCount"
Write-Host "KerbFail:    $KerbFailCount"
Write-Host ""

if ($BruteForceAlerts.Count -gt 0) {
    Write-Host "Brute force detectat:"
    $BruteForceAlerts | Format-Table -AutoSize
} else {
    Write-Host "Nu au fost detectate pattern-uri brute force peste prag."
}

if ($NtlmCount -gt 0) {
    Write-Host ""
    Write-Host "ATENTIE: Exista evenimente NTLM (4776) - posibil fallback de la Kerberos."
}

if ($KerbFailCount -gt 0) {
    Write-Host ""
    Write-Host "ATENTIE: Exista esecuri Kerberos (4771) - verifica SPN, parole, clock skew."
}

vineri, 17 aprilie 2026

Google Apps Script : ... youtube script for dashboard.

Today, I tested Google Apps Script to get data from youtube dashboard, because I make some streams on youtube and another social platforms.
The source code is large, but I will show this part to understand the basics:
function getChannelStats() {
  const response = YouTube.Channels.list(
    "snippet,statistics",
    { mine: true }
  );

  const ch = response.items[0];

  return {
    title: ch.snippet.title,
    subs: Number(ch.statistics.subscriberCount),
    views: Number(ch.statistics.viewCount),
    videos: Number(ch.statistics.videoCount)
  };
} ...

vineri, 10 aprilie 2026

FASM : mouse crosshair with lines on the window GDI.

For a year now, it keeps hacking me on Windows. And when I am in a bad mood, I turn to complex things. Today, I wanted to see if I could make a functional assembly code with Microsoft's Copilot artificial intelligence and my assembly knowledge with FASM. Here is what resulted.
The program is a Win32 GUI application written in FASM that draws a crosshair (vertical + horizontal line) following the mouse cursor inside a window classic GDI.
1. Window Setup
The app starts by registering a Win32 window class (WNDCLASSEX) and creating a standard overlapped window. Nothing unusual here — just the classic Win32 boilerplate.
2. Message Loop
The program enters the main loop:
  • GetMessage
  • TranslateMessage
  • DispatchMessage
This keeps the window responsive and forwards events to the window procedure.
3. Tracking the Mouse
Whenever the mouse moves inside the window, Windows sends a WM_MOUSEMOVE message. The program extracts the X and Y coordinates from lParam:
  • LOWORD(lParam) → X
  • HIWORD(lParam) → Y
These values are stored and the window is invalidated so it can be repainted.
4. Drawing the Crosshair (GDI)
All drawing happens inside WM_PAINT using classic GDI:
  • MoveToEx
  • LineTo
  • Ellipse
  • TextOut
  • FillRect
The steps are:
  • Clear the client area
  • Get the window’s current size (GetClientRect)
  • Draw a vertical line at the mouse’s X position
  • Draw a horizontal line at the mouse’s Y position
  • Draw a small circle at the intersection
  • Print the coordinates in the corner
Because the client size is read dynamically, the crosshair always stretches across the entire window — no matter how it’s resized.
See the source code
; mouse_crosshair.asm
format PE GUI 4.0
entry start
include "win32a.inc"

WM_MOUSEMOVE = 0x0200

section '.data' data readable writeable

    szClass db "MouseGraph",0
    szTitle db "Mouse Crosshair Demo",0

    mouseX dd 0
    mouseY dd 0

    fmt    db "X=%d  Y=%d",0
    buffer db 64 dup(0)

    rc  RECT
    ps  PAINTSTRUCT
    wc  WNDCLASSEX
    msg MSG

section '.code' code readable executable

start:
    invoke GetModuleHandle,0
    mov [wc.hInstance],eax

    mov [wc.cbSize],sizeof.WNDCLASSEX
    mov [wc.style],CS_HREDRAW or CS_VREDRAW
    mov [wc.lpfnWndProc],WndProc
    mov [wc.cbClsExtra],0
    mov [wc.cbWndExtra],0
    mov [wc.hIcon],0
    mov [wc.hIconSm],0
    mov [wc.lpszMenuName],0
    mov [wc.lpszClassName],szClass
    mov [wc.hbrBackground],COLOR_WINDOW+1

    invoke LoadCursor,0,IDC_ARROW
    mov [wc.hCursor],eax

    invoke RegisterClassEx,wc

    invoke CreateWindowEx,0,szClass,szTitle,\
           WS_VISIBLE+WS_OVERLAPPEDWINDOW,\
           200,200,800,600,0,0,[wc.hInstance],0

msg_loop:
    invoke GetMessage,msg,0,0,0
    test eax,eax
    jz exit
    invoke TranslateMessage,msg
    invoke DispatchMessage,msg
    jmp msg_loop

exit:
    invoke ExitProcess,0

; ---------------------------------------------------------
proc WndProc hWnd,uMsg,wParam,lParam

    cmp [uMsg],WM_MOUSEMOVE
    je .mouse

    cmp [uMsg],WM_PAINT
    je .paint

    cmp [uMsg],WM_DESTROY
    je .destroy

.def:
    invoke DefWindowProc,[hWnd],[uMsg],[wParam],[lParam]
    ret

; ---------------- WM_MOUSEMOVE ----------------
.mouse:
    mov eax,[lParam]
    and eax,0FFFFh
    mov [mouseX],eax

    mov eax,[lParam]
    shr eax,16
    and eax,0FFFFh
    mov [mouseY],eax

    invoke InvalidateRect,[hWnd],0,TRUE
    ret

; ---------------- WM_PAINT ----------------
.paint:
    invoke BeginPaint,[hWnd],ps
    mov esi,eax ; hdc

    ; clean client
    invoke GetClientRect,[hWnd],rc
    invoke FillRect,esi,rc,COLOR_WINDOW+1

    ; pen red
    invoke CreatePen,PS_SOLID,1,0x0000FF
    mov ebx,eax
    invoke SelectObject,esi,ebx

    ; ---------------- linie verticala (cruce) ----------------
    mov eax,[mouseX]        ; X constant
    mov edx,[rc.bottom]     ; Y max (jos)
    invoke MoveToEx,esi,eax,0,0
    invoke LineTo,esi,eax,edx

    ; ---------------- line  ----------------
    mov eax,[mouseY]        ; Y constant
    mov edx,[rc.right]      ; X max (dreapta)
    invoke MoveToEx,esi,0,eax,0
    invoke LineTo,esi,edx,eax

    ; punct în intersec?ie
    mov eax,[mouseX]
    mov edx,[mouseY]
    invoke Ellipse,esi,eax-4,edx-4,eax+4,edx+4

    ; text coordonate
    invoke wsprintf,buffer,fmt,[mouseX],[mouseY]
    invoke lstrlen,buffer
    invoke TextOut,esi,10,10,buffer,eax

    invoke EndPaint,[hWnd],ps
    ret

; ---------------- WM_DESTROY ----------------
.destroy:
    invoke PostQuitMessage,0
    ret

endp

section '.idata' import data readable
library kernel32,"KERNEL32.DLL",\
        user32,"USER32.DLL",\
        gdi32,"GDI32.DLL"

include "api/kernel32.inc"
include "api/user32.inc"
include "api/gdi32.inc"

joi, 26 martie 2026

Tools : remove language keyboard with powewrshell.

Simple powershell script with graphic user interface for remove language keyboard. I used copilot artificial intelligence to help me to create this powershell script.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# ============================
# STARTUP MESSAGE
# ============================
[System.Windows.Forms.MessageBox]::Show(
"Run this script as Administrator.

If scripts are blocked, run:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass",
"IMPORTANT",
"OK",
"Information"
)

# ============================
# GET INSTALLED LANGUAGES
# ============================
$languages = Get-WinUserLanguageList

# Build a list of IMTs with language names
$imtList = @()

foreach ($lang in $languages) {
    foreach ($imt in $lang.InputMethodTips) {
        $entry = [PSCustomObject]@{
            LanguageName = $lang.Autonym
            LanguageTag  = $lang.LanguageTag
            IMT          = $imt
        }
        $imtList += $entry
    }
}

# ============================
# BUILD THE FORM
# ============================
$form = New-Object System.Windows.Forms.Form
$form.Text = "Keyboard Layout Selector"
$form.Size = New-Object System.Drawing.Size(520, 600)
$form.StartPosition = "CenterScreen"

$label = New-Object System.Windows.Forms.Label
$label.Text = "Select the keyboard layouts you want to KEEP:"
$label.AutoSize = $true
$label.Location = New-Object System.Drawing.Point(10,10)
$form.Controls.Add($label)

$panel = New-Object System.Windows.Forms.Panel
$panel.Location = New-Object System.Drawing.Point(10,40)
$panel.Size = New-Object System.Drawing.Size(480,460)
$panel.AutoScroll = $true
$form.Controls.Add($panel)

$checkboxes = @()
$y = 10

foreach ($item in $imtList) {
    $cb = New-Object System.Windows.Forms.CheckBox
    $cb.Text = "$($item.LanguageName)  |  $($item.LanguageTag)  |  $($item.IMT)"
    $cb.Location = New-Object System.Drawing.Point(10, $y)
    $cb.AutoSize = $true
    $panel.Controls.Add($cb)
    $checkboxes += $cb
    $y += 30
}

$button = New-Object System.Windows.Forms.Button
$button.Text = "Apply"
$button.Location = New-Object System.Drawing.Point(200,520)
$button.Size = New-Object System.Drawing.Size(100,30)
$form.Controls.Add($button)

# ============================
# APPLY BUTTON LOGIC
# ============================
$button.Add_Click({
    $selectedIMTs = @()

    foreach ($cb in $checkboxes) {
        if ($cb.Checked) {
            # Extract IMT from checkbox text
            $parts = $cb.Text.Split("|")
            $imt = $parts[2].Trim()
            $selectedIMTs += $imt
        }
    }

    if ($selectedIMTs.Count -eq 0) {
        [System.Windows.Forms.MessageBox]::Show("You must select at least one layout to keep.")
        return
    }

    # Build new language list
    $newList = @()

    foreach ($lang in $languages) {
        $newLang = New-WinUserLanguageList $lang.LanguageTag
        $newLang[0].InputMethodTips.Clear()

        foreach ($imt in $lang.InputMethodTips) {
            if ($selectedIMTs -contains $imt) {
                $newLang[0].InputMethodTips.Add($imt)
            }
        }

        if ($newLang[0].InputMethodTips.Count -gt 0) {
            $newList += $newLang[0]
        }
    }

    Set-WinUserLanguageList $newList -Force

    [System.Windows.Forms.MessageBox]::Show("Keyboard layouts updated successfully.")
    $form.Close()
})

# ============================
# SHOW FORM
# ============================
$form.ShowDialog()
NOTE : To resolve issues with the wrong keyboard layout at the sign-in screen, users can follow these steps: Press Win + R to open the Run dialog. Type intl.cpl and click OK to open the Region settings.

miercuri, 18 martie 2026

CodePen : new features on the codepen online I.D.E.

Today. I tested the new codepen.io online I.D.E. and comes with changes more to real development area and new features blocks.

Tools : FASM-Tutorial source code by Mori-TM.

Today, I found this repo with few basic source code for Flat Assembler (FASM).

duminică, 1 februarie 2026

Google Apps Script : my activity on blogger with Blogger API.

Here in the attached image is an informative chart about my activity with posts on my bloggers and the completion trend. This chart was created with Google Apps Script and the Blogger API.

marți, 23 decembrie 2025

Google Apps Script : ... demonstration of the chart feature.

One simple example, see this part of source code:
 // ✅ Chart cu legendă nativă și labelInLegend pentru fiecare serie
 // ✅ Chart with native legend and labelInLegend for each series
  const chart = chartSheet.newChart()
    .setChartType(Charts.ChartType.COMBO)
    .addRange(range)
    .setPosition(1, 1, 0, 0)
    .setOption("title", "BT Funds Evolution Over Time")
    .setOption("legend", { position: "right" })
    .setOption("width", 1600)
    .setOption("height", 900)
    .setOption("seriesType", "line")
    .setOption("hAxis", { title: "Date" })
    .setOption("vAxis", { title: "Value / Return (%)" })

    // ✅ LEGENDĂ NATIVĂ CU LABEL IN LEGEND
    // ✅ NATIVE LEGEND WITH LABEL IN LEGEND
    .setOption("series", {
      0: { labelInLegend: "VUAN",      color: "#1f77b4", pointSize: 5 },
      1: { labelInLegend: "30 zile",   color: "#ff7f0e", pointSize: 5 },
      2: { labelInLegend: "YTD",       color: "#2ca02c", pointSize: 5 },
      3: { labelInLegend: "365 zile",  color: "#d62728", pointSize: 5 },
      4: { labelInLegend: "3 ani",     color: "#9467bd", pointSize: 5 }
    })
See the result

vineri, 19 decembrie 2025

Google Apps Script : ... get data from protected websites with scrapingbee A.P.I. and GAScript.

Simple example with scrapingbee A.P.I. and GAScript to get data from the btassetmanagement.ro website.
Many financial websites — including BT Asset Management — use several layers of protection to prevent automated scraping. These protections are not meant to block normal users, but to stop bots, crawlers, and automated tools from extracting data at high speed.
Web Application Firewall that detects:
  • unusual request patterns
  • requests without browser headers
  • requests from datacenter IPs
  • too many requests in a short time
  • missing cookies or session tokens
If the request looks like a bot, the firewall returns

vineri, 12 decembrie 2025

News : facebook provided largest high quality molecular crystal DFT datase under CC-BY-4.0 License.

OMC25 represents the largest high quality molecular crystal DFT dataset. OMC25 was generated at the PBE-D3 level of theory as implemented in Vienna Ab initio Simulation Package (VASP). OMC25 includes structures sampled from relaxation trajectories of molecular crystals generated by Genarris 3.0 starting from molecules in the OE62 dataset. For more details on the dataset, see arXiv.
The paper "Open Molecular Crystals 2025 (OMC25) Dataset and Models" on arXiv (ID: 2508.02651) is authored by a team that includes researchers affiliated with Meta (Facebook) AI Research.
The OMC25 dataset is provided under a CC-BY-4.0 License.

joi, 20 noiembrie 2025

Google Apps Script : keep sheets whose names include today’s date ...

Today, this simple example will keep sheets whose names include today’s date in flexible regex formats
The script not include the month names (e.g., “20-Nov-2025”)
On running keeps sheets whose names contain any of these combinations; deletes the rest.
Let's see the source code:
function deleteSheetsWithoutToday() {
// Get today's components (zero-padded and variants)
var now = new Date();
var tz = Session.getScriptTimeZone();

var dd = Utilities.formatDate(now, tz, "dd"); // e.g., "20"
var d = Utilities.formatDate(now, tz, "d"); // e.g., "20" (no leading zero if <10)
var mm = Utilities.formatDate(now, tz, "MM"); // e.g., "11"
var m = Utilities.formatDate(now, tz, "M"); // e.g., "11" (no leading zero if <10)
var yyyy = Utilities.formatDate(now, tz, "yyyy"); // e.g., "2025"
var yy = Utilities.formatDate(now, tz, "yy"); // e.g., "25"

// Build a regex that matches many possible date embeddings in the sheet name
var dateRegex = buildFlexibleDateRegex({ dd: dd, d: d, mm: mm, m: m, yyyy: yyyy, yy: yy });

// Active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();

var kept = 0;
var deleted = 0;

sheets.forEach(function(sheet) {
var name = sheet.getName();

// If the name contains a combination of today's date components (any of the supported formats), keep it
if (dateRegex.test(name)) {
kept++;
Logger.log("KEEP: " + name);
} else {
ss.deleteSheet(sheet);
deleted++;
Logger.log("DELETE: " + name);
}
});

Logger.log("Summary → Kept: " + kept + ", Deleted: " + deleted);
}

/**
* Build a flexible regex that matches today's date in common formats.
* It covers:
* - dd[sep]mm[sep](yyyy|yy)
* - mm[sep]dd[sep](yyyy|yy)
* - (yyyy|yy)[sep]mm[sep]dd
* - (yyyy|yy)[sep]dd[sep]mm
* - contiguous forms like ddmmyyyy, mmddyyyy, yyyymmdd, ddmmyy, etc.
* - allows multiple separator types: -, _, ., /, space, colon
* - accepts zero-padded and non-padded day/month (e.g., "7" or "07")
*/
function buildFlexibleDateRegex(parts) {
var dd = parts.dd; // zero-padded day
var d = parts.d; // non-padded day
var mm = parts.mm; // zero-padded month
var m = parts.m; // non-padded month
var yyyy = parts.yyyy;
var yy = parts.yy;

// Separator class: one or more of -, _, ., /, space, or colon
var SEP = "[\\-_.\\/\\s:]+";

// Day and month alternatives (padded or not)
var DAY = "(?:" + dd + "|" + d + ")";
var MONTH = "(?:" + mm + "|" + m + ")";
var YEAR = "(?:" + yyyy + "|" + yy + ")";

// Ordered patterns with separators
var withSeps = [
DAY + SEP + MONTH + SEP + YEAR, // dd-mm-yyyy or dd/mm/yy, etc.
MONTH + SEP + DAY + SEP + YEAR, // mm-dd-yyyy
YEAR + SEP + MONTH + SEP + DAY, // yyyy-mm-dd
YEAR + SEP + DAY + SEP + MONTH // yyyy-dd-mm
];

// Contiguous patterns (no separators)
var noSeps = [
dd + mm + yyyy,
dd + mm + yy,
mm + dd + yyyy,
mm + dd + yy,
yyyy + mm + dd,
yy + mm + dd
];

// Optional surrounding non-digit boundaries to avoid matching inside longer numbers
// We’ll use word boundaries plus lookarounds to be more permissive with symbols.
var prefix = "(?<!\\d)"; // no digit before
var suffix = "(?!\\d)"; // no digit after

// Combine all patterns into a single alternation
var combined =
prefix +
"(?:" +
withSeps.join("|") +
"|" +
noSeps.join("|") +
")" +
suffix;

// Make the regex case-insensitive and global
return new RegExp(combined, "i");
}

miercuri, 12 noiembrie 2025

Google Apps Script : how to filter data by the value in a cell and reset the filter.

If you look at this blog, you will find examples of the results of running GAScript scripts in Excel. Today, I will show you how to filter the entire table based on a value from a cell and how to reset it to the original result without filtering.
function reseteazaFiltrarea() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const ultimaLinie = sheet.getLastRow();
  sheet.showRows(1, ultimaLinie);
  SpreadsheetApp.getUi().alert("Toate rândurile au fost afișate.");
}

function filtreazaRanduriDupaCelulaSelectata() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const cell = sheet.getActiveCell();
  const valoare = cell.getValue();
  const coloanaDeFiltrare = cell.getColumn();

  const ultimaLinie = sheet.getLastRow();

  for (let i = 1; i <= ultimaLinie; i++) {
    const valoareRand = sheet.getRange(i, coloanaDeFiltrare).getValue();
    sheet.showRows(i); // asigură-te că rândul e vizibil
    if (valoareRand !== valoare && i !== cell.getRow()) {
      sheet.hideRows(i);
    }
  }

  SpreadsheetApp.getUi().alert(`Filtrare aplicată pentru: ${valoare}`);
}

joi, 19 iunie 2025

News : Google Apps Script - get products by region into new sheet.

... this is source code for search products on my region using google apps script:
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Ocazii Scraper')
    .addItem('Scrape placa-de-baza', 'scrapeProcess_placa_de_baza')
    .addToUi();
}

function scrapeProcess_placa_de_baza() {
  const url_placa_de_baza = "https://www.okazii.ro/componente-computere/placa-de-baza/?judete_lp=35&sort=pret_asc";
  let html;
  try {
    const response = UrlFetchApp.fetch(url_placa_de_baza);
    html = response.getContentText();
  } catch (error) {
    Logger.log("Error fetching HTML: " + error.message);
    return;
  }

  const itemRegex = /<div class="list-item[\s\S]*?<h2>[\s\S]*?<a[^>]+href="(.*?)"[^>]+title="(.*?)"[\s\S]*?<span class="prSup"><span>(\d+)<\/span>[\s\S]*?<span class="prList"><span>([\d,]+)<\/span>/g;

  const spreadsheetName = "placa_baza_200625";
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName(spreadsheetName) || spreadsheet.insertSheet(spreadsheetName);

  // Adaugă headere dacă e un sheet nou
  if (sheet.getLastRow() === 0) {
    sheet.appendRow(["Data", "Titlu", "Href", "Pret", "Livrare"]);
  }

  const now = new Date();
  const formattedDate = Utilities.formatDate(now, SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "ddMMyy");

  let match;
  while ((match = itemRegex.exec(html)) !== null) {
    let [_, href, title, price, delivery] = match;
    delivery = delivery.replace(",", "."); // înlocuiește virgula pentru formatare numerica
    const row = [formattedDate, title, href, price, delivery];
    sheet.appendRow(row);
  }
}

joi, 12 iunie 2025

News : FOSDEM 2025 Talk: From Pixels to Procedures.

Graphite is a free, open source vector and raster graphics editor, available now in alpha. Get creative with a fully nondestructive editing workflow that combines layer-based compositing with node-based generative design.
The GitHub repo for this project can be found on the github repo - GraphiteEditor/Graphite.

miercuri, 16 aprilie 2025

News : Google Apps Script - add movies from website into new sheet.

This source code with GAScript add few movies from cinemagia website:
The commant the lines from the last post tutorial and use this source code.
This will add movies into separated sheet by date, see the source code:
  // try {
  //   const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  //   const currentDate = new Date();
  //   extractedData.forEach((movie, rowIndex) =&gt; {
  //     if (movie.title !== "N/A" && movie.image !== "N/A") {
  //       const imageFormula = `=IMAGE("${movie.image}")`;
  //       const rowIndexAdjusted = sheet.getLastRow() + 1;
  //       sheet.appendRow([currentDate, movie.title, imageFormula, movie.channel, movie.time]);
  //       sheet.setRowHeight(rowIndexAdjusted, 76); // Set row height to 330px
  //     }
  //   });
  //   Logger.log("Processed movies count: ", extractedData.length);
  // } catch (error) {
  //   Logger.log("Error adding data to spreadsheet: ", error.message);
  // }
  try {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const currentDate = new Date();
  
  // Formatăm data curentă pentru numele sheet-ului
  const formattedDate = Utilities.formatDate(currentDate, SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone(), "yyyy-MM-dd");
  const sheetName = `Data-${formattedDate}`;
  
  // Verificăm dacă sheet-ul cu acest nume există deja
  let sheet = spreadsheet.getSheetByName(sheetName);
  if (!sheet) {
    // Creăm un nou sheet dacă nu există
    sheet = spreadsheet.insertSheet(sheetName);
  }
  
  extractedData.forEach((movie) =&gt; {
    if (movie.title !== "N/A" && movie.image !== "N/A") {
      const imageFormula = `=IMAGE("${movie.image}")`;
      const rowIndexAdjusted = sheet.getLastRow() + 1;
      sheet.appendRow([currentDate, movie.title, imageFormula, movie.channel, movie.time]);
      
      // Păstrăm formatarea originală pentru înălțimea rândurilor
      sheet.setRowHeight(rowIndexAdjusted, 76); 
    }
  });
} catch (error) {
  console.error("A apărut o eroare:", error.message);
}

sâmbătă, 29 martie 2025

News : Google Apps Script - add movies from website.

This source code with GAScript add few movies from cinemagia website:
This is the source code:
function scrapeProcessAndCleanUp() {
  const url = "https://www.cinemagia.ro/program-tv/filme-la-tv/filme-pro-tv,antena-1,tvr-1,prima-tv,diva,pro-cinema,film-cafe/azi/";
  
  let html;
  try {
    const response = UrlFetchApp.fetch(url);
    html = response.getContentText();
    Logger.log("Fetched HTML content length: ", html.length);
  } catch (error) {
    Logger.log("Error fetching HTML content: ", error.message);
    return;
  }

  let doc;
  try {
    doc = DocumentApp.create("Temporary HTML Content");
    doc.appendParagraph(html);
    doc.saveAndClose();
    Logger.log("Document created successfully with ID: ", doc.getId());
  } catch (error) {
    Logger.log("Error creating/saving document: ", error.message);
    return;
  }

  let text;
  try {
    const document = DocumentApp.openById(doc.getId());
    text = document.getBody().getText();
    Logger.log("Document text content length: ", text.length);
  } catch (error) {
    Logger.log("Error opening document or extracting text: ", error.message);
    return;
  }

  const titles = [...text.matchAll(/&lt;li class="first"&gt;(.*?)&lt;\/li&gt;/g)];
  const images = [...text.matchAll(/&lt;img src="(https:\/\/static\.cinemagia\.ro\/img\/resize\/db\/movie.*?)"/g)];
  const channels = [...text.matchAll(/&lt;span class="r1"&gt;(.*?)&lt;\/span&gt;/g)];
  const times = [...text.matchAll(/&lt;span class="r2"&gt;(.*?)&lt;\/span&gt;/g)];
  Logger.log("Titles found: ", titles.length);
  Logger.log("Images found: ", images.length);
  Logger.log("Channels found: ", channels.length);
  Logger.log("Times found: ", times.length);


  const extractedData = titles.map((title, index) =&gt; {
    const image = images[index] ? images[index][1] : "N/A";
    const channel = channels[index] ? channels[index][1].trim() : "N/A";
    const time = times[index] ? times[index][1].trim() : "N/A";
    return {
      title: title[1].trim(),
      image: image,
      channel: channel,
      time: time
    };
  });
  try {
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
    const currentDate = new Date();
    extractedData.forEach((movie, rowIndex) =&gt; {
      if (movie.title !== "N/A" && movie.image !== "N/A") {
        const imageFormula = `=IMAGE("${movie.image}")`;
        const rowIndexAdjusted = sheet.getLastRow() + 1;
        sheet.appendRow([currentDate, movie.title, imageFormula, movie.channel, movie.time]);
        sheet.setRowHeight(rowIndexAdjusted, 76); // Set row height to 330px
      }
    });
    Logger.log("Processed movies count: ", extractedData.length);
  } catch (error) {
    Logger.log("Error adding data to spreadsheet: ", error.message);
  }
}

marți, 25 martie 2025

News : Google Apps Script - find duplicate files in google drive.

For today, a simple GAScript source code to add into spreadsheet the duplicate files from Google drive.
This Google Apps Script finds duplicate files in Google Drive by comparing file sizes and optionally file types. It then displays the results in a spreadsheet with detailed information about each duplicate file. The script collects information about all files in Drive. Files are grouped by their size and optionally file type. Any group with more than one file is considered a set of duplicates These duplicate sets are displayed in the spreadsheet
Functions
  • checkDuplicatesInDrive(): Main function that searches your entire Google Drive for duplicates
  • checkDuplicatesInFolder(): Alternative function that searches a specific folder and its subfolders
  • findDuplicateFiles(): Core function that identifies duplicate files based on size and type
  • addDuplicatesToSheet(): Adds the found duplicates to a spreadsheet
I used artificial inteligence and this help me much ...
/**
 * Main function to check files in the root folder and add duplicates to the active spreadsheet
 */
function checkDuplicatesInDrive() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  
  // Clear the sheet and set up headers
  sheet.clear();
  sheet.appendRow(["Group", "File Name", "Size", "Type", "File Path", "Date Created", "Last Updated", "URL"]);
  sheet.getRange(1, 1, 1, 8).setFontWeight("bold").setBackground("#f3f3f3");
  
  // Find all duplicates
  const duplicates = findDuplicateFiles(true);
  
  // Check if any duplicates were found
  if (Object.keys(duplicates).length === 0) {
    sheet.appendRow(["No duplicate files found"]);
    sheet.autoResizeColumns(1, 8);
    return;
  }
  
  // Add duplicates to the sheet
  addDuplicatesToSheet(duplicates, sheet);
  
  // Auto-resize columns
  sheet.autoResizeColumns(1, 8);
}

/**
 * Adds duplicate files to the specified sheet
 */
function addDuplicatesToSheet(duplicates, sheet) {
  let groupNumber = 1;
  let rowIndex = 2;
  let totalDuplicateFiles = 0;
  
  for (const key in duplicates) {
    const files = duplicates[key];
    totalDuplicateFiles += files.length;
    
    files.forEach((file, index) => {
      // Get file path
      const filePath = getFilePath(file.id);
      
      sheet.appendRow([
        groupNumber,
        file.name,
        formatFileSize(file.size),
        file.mimeType,
        filePath,
        file.dateCreated.toLocaleString(),
        file.lastUpdated.toLocaleString(),
        file.url
      ]);
      
      // Add hyperlink to the file URL
      sheet.getRange(rowIndex, 8).setFormula(`=HYPERLINK("${file.url}","Open File")`);
      
      rowIndex++;
    });
    
    groupNumber++;
  }
  
  // Add summary at the bottom - only if we have duplicates
  if (totalDuplicateFiles > 0) {
    sheet.appendRow(["SUMMARY"]);
    sheet.appendRow([`Found ${totalDuplicateFiles} duplicate files in ${groupNumber - 1} groups.`]);
  }
  
  return totalDuplicateFiles;
}

/**
 * Gets the file path for a given file ID
 */
function getFilePath(fileId) {
  try {
    const file = DriveApp.getFileById(fileId);
    const parents = file.getParents();
    
    if (parents.hasNext()) {
      const parent = parents.next();
      return getFolderPath(parent) + "/" + file.getName();
    } else {
      return "/" + file.getName();
    }
  } catch (e) {
    return "Path not available";
  }
}

/**
 * Gets the folder path for a given folder
 */
function getFolderPath(folder) {
  try {
    const parents = folder.getParents();
    
    if (!parents.hasNext()) {
      return "/" + folder.getName();
    }
    
    const parent = parents.next();
    return getFolderPath(parent) + "/" + folder.getName();
  } catch (e) {
    return "/Unknown";
  }
}

/**
 * Finds duplicate files in Google Drive based on file size and optionally file type.
 * @param {boolean} considerFileType Whether to consider file type when finding duplicates (default: true)
 * @param {string} folderId Optional folder ID to search in. If not provided, searches in the entire Drive.
 * @return {Object} An object containing groups of duplicate files
 */
function findDuplicateFiles(considerFileType = true, folderId = null) {
  // Create a map to store files by their size (and optionally type)
  const fileMap = {};
  
  // Get files to check
  let files;
  if (folderId) {
    const folder = DriveApp.getFolderById(folderId);
    files = folder.getFiles();
  } else {
    files = DriveApp.getFiles();
  }
  
  // Process each file
  while (files.hasNext()) {
    const file = files.next();
    // Skip Google Docs, Sheets, etc. as they don't have a fixed size
    if (file.getSize() === 0) continue;
    
    const fileSize = file.getSize();
    const mimeType = file.getMimeType();
    
    // Create a key based on file size and optionally type
    let key = fileSize.toString();
    if (considerFileType) {
      key += '_' + mimeType;
    }
    
    // Add file to the map
    if (!fileMap[key]) {
      fileMap[key] = [];
    }
    
    fileMap[key].push({
      id: file.getId(),
      name: file.getName(),
      size: fileSize,
      mimeType: mimeType,
      url: file.getUrl(),
      dateCreated: file.getDateCreated(),
      lastUpdated: file.getLastUpdated()
    });
  }
  
  // Filter out unique files (groups with only one file)
  const duplicates = {};
  for (const key in fileMap) {
    if (fileMap[key].length > 1) {
      duplicates[key] = fileMap[key];
    }
  }
  
  return duplicates;
}

/**
 * Alternative function to check files in a specific folder and its subfolders
 */
function checkDuplicatesInFolder() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  
  // Clear the sheet and set up headers
  sheet.clear();
  sheet.appendRow(["Group", "File Name", "Size", "Type", "File Path", "Date Created", "Last Updated", "URL"]);
  sheet.getRange(1, 1, 1, 8).setFontWeight("bold").setBackground("#f3f3f3");
  
  // Collect all files from the folder and subfolders
  var fileMap = {};
  var rootFolder = DriveApp.getRootFolder(); // Change this to your specific folder if needed
  collectFilesFromFolder(rootFolder, fileMap);
  
  // Filter out unique files
  const duplicates = {};
  for (const key in fileMap) {
    if (fileMap[key].length > 1) {
      duplicates[key] = fileMap[key];
    }
  }
  
  // Check if any duplicates were found
  if (Object.keys(duplicates).length === 0) {
    sheet.appendRow(["No duplicate files found"]);
    sheet.autoResizeColumns(1, 8);
    return;
  }
  
  // Add duplicates to the sheet
  addDuplicatesToSheet(duplicates, sheet);
  
  // Auto-resize columns
  sheet.autoResizeColumns(1, 8);
}

/**
 * Recursively collects files from a folder and its subfolders
 */
function collectFilesFromFolder(folder, fileMap, considerFileType = true) {
  // Process files in this folder
  var files = folder.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    // Skip Google Docs, Sheets, etc. as they don't have a fixed size
    if (file.getSize() === 0) continue;
    
    const fileSize = file.getSize();
    const mimeType = file.getMimeType();
    
    // Create a key based on file size and optionally type
    let key = fileSize.toString();
    if (considerFileType) {
      key += '_' + mimeType;
    }
    
    // Add file to the map
    if (!fileMap[key]) {
      fileMap[key] = [];
    }
    
    fileMap[key].push({
      id: file.getId(),
      name: file.getName(),
      size: fileSize,
      mimeType: mimeType,
      url: file.getUrl(),
      dateCreated: file.getDateCreated(),
      lastUpdated: file.getLastUpdated()
    });
  }
  
  // Process subfolders
  var subfolders = folder.getFolders();
  while (subfolders.hasNext()) {
    var subfolder = subfolders.next();
    collectFilesFromFolder(subfolder, fileMap, considerFileType);
  }
}

/**
 * Helper function to format file size in a human-readable format
 */
function formatFileSize(bytes) {
  if (bytes < 1024) return bytes + " bytes";
  else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";
  else if (bytes < 1073741824) return (bytes / 1048576).toFixed(2) + " MB";
  else return (bytes / 1073741824).toFixed(2) + " GB";
}

/**
 * Creates a new Google Spreadsheet with the duplicate files report
 */
function createDuplicateFilesSpreadsheet() {
  const duplicates = findDuplicateFiles(true);
  
  // Create a new spreadsheet
  const ss = SpreadsheetApp.create("Duplicate Files Report - " + new Date().toLocaleString());
  const sheet = ss.getActiveSheet();
  
  // Set up headers
  sheet.appendRow(["Group", "File Name", "Size", "Type", "File Path", "Date Created", "Last Updated", "URL"]);
  
  // Format header row
  sheet.getRange(1, 1, 1, 8).setFontWeight("bold").setBackground("#f3f3f3");
  
  // Check if any duplicates were found
  if (Object.keys(duplicates).length === 0) {
    sheet.appendRow(["No duplicate files found"]);
    sheet.autoResizeColumns(1, 8);
    return ss.getUrl();
  }
  
  // Add duplicates to the sheet
  addDuplicatesToSheet(duplicates, sheet);
  
  // Auto-resize columns
  sheet.autoResizeColumns(1, 8);
  
  Logger.log(`Spreadsheet created: ${ss.getUrl()}`);
  return ss.getUrl();
}
See the result into the spreadsheet: