Arquivo da categoria: Códigos aleatórios

Script para baixar documentos do Issuu no GNU/Linux

Tive necessidade de baixar um documento do Issuu. Segue um script simples que escrevi para baixar as páginas, convertê-las para PDF e mesclá-las. Ele não tem checagem de erros, mas pode ser útil para mais pessoas:

#!/bin/bash
 
if [ $# -lt 1 ]; then
    echo "Uso: $0 <endereco_do_documento_no_issuu>"
    exit
fi
 
tmp=$(mktemp -d)
 
echo "Baixando pagina HTML..."
wget -q "$1" -O $tmp/html
 
pageCount=$(cat $tmp/html | grep -o '"pageCount":[0-9]*' | sed 's/.*://')
model=$(cat $tmp/html | grep 'image_src' | sed 's/.*href="//; s/".*//')
title=$(cat $tmp/html | grep '<title>' | sed 's/.*<title>//; s/<\/title>.*//')
 
echo "-> Encontrado documento de $pageCount paginas"
echo "-> Primeira pagina: $model"
 
for i in $(seq 1 $pageCount); do
    download=$(echo $model | sed "s/page_1/page_$i/")
    echo "Baixando pagina ${i}..."
    wget -q "$download" -O "$tmp/page_${i}.jpg"
done
 
echo "Convertendo paginas JPG -> PDF..."
for i in $(ls $tmp/*.jpg); do
    convert "$i" "${i}.pdf"
done
 
echo "Mesclando paginas..."
gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile="${title}.pdf" $tmp/page_*.pdf
rm -rf $tmp
 
echo "-> Pronto: '${title}.pdf'"

O script requer Bash, wget, GhostScript e ImageMagick. A maioria das distribuições de Linux já tem esses aplicativos, mas por via das dúvidas cheque se você tem o ImageMagick instalado.

Download do script: issuu_download.sh (932 bytes)

Para instalar, é só baixar o arquivo, torná-lo executável e movê-lo para alguma pasta do seu $PATH:

$ wget http://tiagomadeira.com/wp-content/uploads/2014/09/issuu_download.sh
$ chmod +x issuu_download.sh
$ sudo mv issuu_download.sh /bin

Para usar, é só digitar:

$ issuu_download.sh <endereco_do_documento>

downloadsubtitle: script para baixar legendas de filmes automaticamente no shell do GNU/Linux

Me acostumei a usar o legendas.tv para baixar legendas de filmes e acabei nunca me perguntando se haveria um jeito mais fácil de baixá-las. Hoje fui assistir um filme e, ao entrar no navegador para baixar sua legenda, me deparei com a mensagem de que o site estava fora do ar:

legendas

A situação me obrigou a procurar outros sites e outras formas de baixar legendas. Minha primeira ideia foi usar o opensubtitles.org, que já havia usado algumas outras vezes. Chegando lá e procurando pelo filme que eu desejava, vi muitas opções e não estava muito claro que legenda baixar para a versão do filme que eu tinha.

Então resolvi dar uma fuçada na pesquisa avançada do site, onde acabei encontrando uma pesquisa por hash. Hash, em computação, é uma função que “resume” uma informação gigante (tipo um arquivo bem grande) numa informação bem pequena (tipo 16 caracteres) que o represente de forma única (ou quase única). A pesquisa por hash, no caso desse site, consiste em procurar uma legenda utilizando esse “ID” do arquivo (ou seja, não importa seu nome).

Achei a possibilidade tão legal que resolvi fazer um programa para nunca mais precisar abrir o navegador quando eu quiser baixar a legenda de um filme. Escrevi um minúsculo programa em C chamado oshash (de OpenSubtitles Hash) para calcular o hash de um filme de acordo com a especificação do site (que não requer nada, a não ser um compilador de C e a biblioteca padrão) e um script (bem tosco, mas funcional) chamado downloadsubtitle que usa o programa oshash (e pequenos programas que todo mundo tem, tipo grep, sed, wget e unzip) para baixar a legenda.

O funcionamento ficou bem fácil: para baixar uma legenda em qualquer língua, basta você digitar downloadsubtitle arquivo.avi para baixar a legenda do “arquivo.avi” (que já vai ser automaticamente nomeada como “arquivo.srt”). Se você quiser especificar uma língua (por exemplo, português do Brasil), é só digitar downloadsubtitle arquivo.avi pob (pob é o código do português do Brasil). Se você quiser baixar uma legenda em inglês ou espanhol, pode usar downloadsubtitle arquivo.avi eng,esp.

Exemplo de funcionamento

$ ls
Amelie [Amélie Poulain].2001.BRRip.x264.AAC[5.1]-VLiS.mkv
$ downloadsubtitle Amelie\ \[Amélie\ Poulain\].2001.BRRip.x264.AAC\[5.1\]-VLiS.mkv pob
Requested language: pob
Movie hash: bcdc90cf4873c09b
Subtitle ID: 4642726
Subtitle: Amelie [Amélie Poulain].2001.BRRip.x264.AAC[5.1]-VLiS.srt
$ ls
Amelie [Amélie Poulain].2001.BRRip.x264.AAC[5.1]-VLiS.mkv  Amelie [Amélie Poulain].2001.BRRip.x264.AAC[5.1]-VLiS.srt
$ 

E aí o filme está pronto para você assistir com o mplayer ou com o seu programa favorito.

Código

Este é o código inicial. Está aqui para fins históricos. Não será atualizado. Use a próxima seção (Download) para baixar a última versão, com bugs corrigidos, tratamento de erros e possivelmente novas funcionalidades.

oshash.c
#include <stdio.h>
#include <stdlib.h>
 
void usage(char *name) {
    printf("Usage: %s <file>\n", name);
    exit(1);
}
 
int main(int argc, char *argv[]) {
    unsigned long long buf[16384], c = 0;
    FILE *in;
    int i;
    if (argc != 2) {
        usage(argv[0]);
    }
    in = fopen(argv[1], "rb");
    if (in == NULL) {
        usage(argv[0]);
    }
    fread(buf, 8192, 8, in);
    fseek(in, -65536, SEEK_END);
    fread(&buf[8192], 8192, 8, in);
    for (i = 0; i < 16384; i++) {
        c+= buf[i];
    }
    c+= ftell(in);
    fclose(in);
    printf("%016llx\n", c);
    return 0;
}
downloadsubtitle
#!/bin/bash
 
usage() {
    echo "Usage: $0 <file> [lang]"
    echo "Examples:"
    echo "$ $0 movie.avi pob         # brazilian portuguese"
    echo "$ $0 movie.avi por,pob     # any portuguese"
    echo "$ $0 movie.avi eng         # english"
    echo "$ $0 movie.avi all         # any language"
    exit
}
 
if [ $# -lt 1 ]; then
    usage
elif [ $# -gt 2 ]; then
    usage
fi
 
if [ $# = 2 ]; then
    lang=$2
else
    lang="any"
fi
 
echo "Requested language: $lang"
output=$(echo "$1" | sed 's/\.[^.]*$/.srt/')
oshash=$(oshash "$1")
echo "Movie hash: $oshash"
subid=$(wget "http://www.opensubtitles.org/en/search/sublanguageid-$lang/moviehash-$oshash/rss_2_00" -q -O - \
    | grep '<link>.*en/subtitles' | sed 's|.*en/subtitles/||; s|/.*||' | head -n1)
echo "Subtitle ID: $subid"
wget "http://www.opensubtitles.org/en/subtitleserve/sub/$subid" -q -O - | gunzip > "$output" 2> /dev/null
echo "Subtitle: $output"

Download

Criei um repositório no Github para colocar o código: github.com/tmadeira/downloadsubtitle

Para quem tem git, é possível baixar com git clone https://github.com/tmadeira/downloadsubtitle.git

Para quem não tem, dá pra baixar em ZIP daqui: github.com/tmadeira/downloadsubtitle/archive/master.zip

O programa ainda não está empacotado bonitinho (não tem nem Makefile ou instruções de instalação). Se futuramente vier a ter, este post será atualizado. Em resumo, basta compilar o código em C (digitando gcc oshash.c -o oshash) e colocar os arquivos oshash e downloadsubtitle numa pasta do seu $PATH (por exemplo, /usr/local/bin).

Sugestões e correções são bem-vindas.

Mostrando uma agenda do Google Calendar no seu site

Escrito em PHP. Pode ser usado no tema do seu WordPress. Requer CURL. Faz cache do calendário para não ter que baixá-lo sempre que alguém entra no seu site. Desenvolvido para um site que vai sair nos próximos dias. Use, modifique e distribua como quiser. (Não me responsabilizo por qualquer problema. Fiz pra um caso específico. A checagem de erros é meio porca.)

<?php
date_default_timezone_set('America/Sao_Paulo');
$events = Array();
$dom = new DOMDocument();
 
$file = "cached_calendar.xml";
 
$last = -1;
if (file_exists($file)) {
    $last = filemtime($file);
}
// Mude 3600 para o tempo (em segundos) que você quiser que o cache expire
if (time() - $last > 3600) {
    $fp = fopen($file, "w+");
    if (!$fp) {
        die();
    }
    // Substitua o e-mail do calendário do Google CodeJam pelo e-mail do seu calendário (público)
    $ch = curl_init("https://www.google.com/calendar/feeds/google.com_jqv7qt9iifsaj94cuknckrabd8%40group.calendar.google.com/public/full");
    curl_setopt($ch, CURLOPT_TIMEOUT, 50);
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_exec($ch);
    curl_close($ch);
    fclose($fp);
}
 
$dom->load($file);
$feed = $dom->getElementsByTagName("feed");
$entries = $feed->item(0)->getElementsByTagName("entry");
foreach ($entries as $entry) {
    $children = $entry->getElementsByTagName("*");
    $day = "";
    $start = "";
    $end = "";
    foreach ($children as $child) {
        switch ($child->tagName) {
        case "title":
            $title = $child->nodeValue;
            break;
        case "gd:when":
            if ($child->hasAttribute("startTime")) {
                $st = strtotime($child->getAttribute("startTime"));
                $time_to_sort = $st;
                $day = date_i18n("l, d/M", $st);
                $start = date("H:i", $st);
            }
            if ($child->hasAttribute("endTime")) {
                $et = strtotime($child->getAttribute("endTime"));
                $end = date("H:i", $et);
            }
            break;
        }
    }
    if ($title != "" && $day != "") {
        $events[] = Array(
            "time_to_sort" => $time_to_sort,
            "day" => $day,
            "start" => $start,
            "end" => $end,
            "title" => $title
        );
    }
}
function cmp($a, $b) {
    $a = $a["time_to_sort"];
    $b = $b["time_to_sort"];
    if ($a == $b) {
        return strcmp($a["title"], $b["title"]);
    }
    return ($a < $b) ? -1 : 1;
}
usort($events, "cmp");
 
$n = count($events);
if ($n > 0) {
    $lastDay = "";
    for ($i = 0; $i < $n; $i++) {
        $day = $events[$i]['day'];
        $title = $events[$i]['title'];
        $start = $events[$i]['start'];
        $end = $events[$i]['end'];
        if ($lastDay != $day) {
            if ($i != 0) {
                echo "</ul>\n\n";
            }
            echo "<h3 class="day"><span>$day</span></h3>\n";
            echo "<ul>\n";
        }
        echo "\t<li>\n";
        if ($start != "") {
            // Você pode modificar aqui para mostrar o horário de término ($end).
            echo "tt<span class="time">{$start}</span>\n";
        }
        echo "\t\t<strong>{$title}</strong>\n";
        echo "\t</li>\n";
        $lastDay = $day;
    }
    echo "</ul>\n";
} else {
    echo "<p>Nenhum evento cadastrado.</p>\n";
}
?>

Dump email addresses from files

Suppose you have a lot of .doc, .docx, .xls, .xlsx, .gz, .bz2, .pdf and text in general (.csv, .txt etc.) files and want to dump all the (unique) email addresses from them. How would you do it? Here is a simple solution I’ve just implemented (and probably didn’t test enough, so tell me if you find any bug):

#!/bin/sh
tmp=$(tempfile)
while [ $# -gt 0 ]; do
    if [ -r "$1" ]; then
        ext=$(echo ${1#*.} | tr [A-Z] [a-z])
        case $ext in
            docx | xlsx)
                # requires: http://blog.kiddaland.net/2009/07/antiword-for-office-2007/
                cat_open_xml "$1" >> $tmp
                ;;
            doc)
                # requires: antiword
                antiword "$1" >> $tmp
                ;;
            xls)
                # requires: catdoc
                xls2csv "$1" >> $tmp
                ;;
            gz)
                cat "$1" | gunzip >> $tmp
                ;;
            bz2)
                cat "$1" | bunzip2 >> $tmp
                ;;
            zip)
                unzip -p "$1" >> $tmp
                ;;
            pdf)
                # requires: xpdf-utils
                t=$(tempfile)
                pdftotext "$1" $t
                cat $t >> $tmp
                rm $t
                ;;
            *)
                text=$(file -b --mime-type "$1" | sed -e 's//.*//')
                if [ "z$text" = "ztext" ]; then
                    cat "$1" >> $tmp
                fi
                ;;
        esac
    fi
    shift
done
cat $tmp | grep -o -E '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}\b' 
         | tr [A-Z] [a-z] | sort -u
rm $tmp

(the email regexp is explained here: regular-expressions.info/email.html)

Como mostrar o último post de cada categoria no WordPress?

ATENÇÃO: Este conteúdo foi publicado há 5 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

O WordPress é um dos meus programas preferidos e um dos que mais consome as madrugadas já faz alguns anos. Escrevi inúmeros temas, alguns plugins (um único genérico o suficiente para ser público) e já modifiquei algumas partes do código (embora hoje seja raro isso ser necessário).

Acho muito simples e me divirto ao programar em PHP para a web (talvez porque eu faça isso há uns dez anos). Gosto muito da forma como o WordPress é escrito e, com efeito, seu lema é Code is poetryCódigo é poesia. Sua documentação e seu código são muito didáticos e foram, assim como seus temas e as tabelas no banco de dados, evoluindo de acordo com o tempo: as atualizações sempre têm novas features e formas mais genéricas e mais elegantes de fazer as coisas.

O WordPress é um software livre usado por mais de 15% da web (um número incrível!) e tem uma comunidade que produz várias extensões (temas e plugins). Gosto tanto dele que certa vez (no final de 2007) escrevi um plugin só para ganhar uma camiseta (que é tamanho G e ainda assim uso de vez em quando).

Propaganda e blogagem a parte, me deparei com um problema interessante em um dos sites que administro com ele no último fim de semana (a gente sempre se depara com problemas interessantes no WordPress também): dado uma categoria com várias subcategorias, gostaria de mostrar um link para o último post de cada uma de suas subcategorias na página inicial.

Uma solução trivial seria fazer uma query pedindo os filhos de uma dada categoria (usando a tabela wp_term_taxonomy) e uma query por categoria para descobrir seu último post (usando as tabelas wp_posts e wp_term_relationships). Suponha (até o final desse post) que a categoria-mãe de todas as categorias que eu quero mostrar na página seja a de ID 33. Então, essa solução seria algo como:

<?php
$query = mysql_query("SELECT term_id FROM wp_term_taxonomy
                      WHERE parent = 33 AND taxonomy = 'category'");
while (list($cid) = mysql_fetch_row($query)) {
    $posts_query = mysql_query("SELECT p.ID, p.post_name, p.post_title
                                FROM wp_posts AS p,
                                     wp_term_relationships AS r
                                WHERE p.ID = r.object_ID AND r.term_taxonomy_id = '$cid'
                                ORDER BY p.post_date DESC
                                LIMIT 1");
    if (mysql_num_rows($posts_query)) {
        list($id, $permalink, $title) = mysql_fetch_row($posts_query);
        // Faça o que quiser com o post aqui
    } else {
        echo "A categoria $cid não tem posts.\n";
    }
}
?>

Não deve ser difícil de entender, mas dá pra resolver de forma ainda mais simples que essa. O WordPress é fantástico e usar as funções dele próprio é bem mais simples, genérico e resolve o problema. A função get_categories aceita um monte de parâmetros, mas só precisamos do child_of:

<?php
foreach (get_categories('child_of=33') as $cat) {
    list($post) = get_posts("numberposts=1&category={$cat->term_id}");
    // Faça o que quiser com o post aqui
}
?>

(quem entrou no post procurando a solução pro problema pode parar por aqui se não for nerd)

Porém, eu queria resolver o problema com uma única query. Achei que seria mais elegante resolver o problema todo no banco de dados sem escrever em PHP e achei que poderia ficar mais rápido. Acho que não ficou mais elegante e não faço ideia se fica mais rápido (fiquei com a impressão de que seja pior porque faço JOIN de quatro tabelas enormes), nem acho que tenha volume de dados (ainda) no site em que implementei isso pra realmente me preocupar, mas me diverti fazendo. Então segue o resultado:

SELECT p.ID AS id,
       CASE WHEN (p.post_date > DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) THEN
           p.post_title
       ELSE
           ''
       END AS title,
       GROUP_CONCAT(c.slug) AS cat
FROM wp_posts AS p
INNER JOIN
    (
        SELECT MAX(p.post_date) AS post_date, c.term_ID AS cid, COUNT(p.ID) AS count FROM
            wp_posts AS p,
            wp_term_relationships AS r,
            wp_terms AS c
        WHERE
            p.ID = r.object_ID AND
            c.term_ID = r.term_taxonomy_ID AND
            p.post_status = 'publish' AND
            p.post_type = 'post'
        GROUP BY c.term_id
    ) AS last ON p.post_date = last.post_date
INNER JOIN wp_term_relationships AS r ON p.ID = r.object_ID
INNER JOIN wp_terms AS c ON c.term_id = r.term_taxonomy_ID
INNER JOIN wp_term_taxonomy AS t ON t.term_id = r.term_taxonomy_ID
WHERE
    c.term_id = last.cid
    AND t.parent = 33
    AND t.taxonomy = 'category'
    AND p.post_status = 'publish'
    AND p.post_type = 'post'
    # AND last.count >= 3
GROUP BY p.ID ORDER BY p.post_date DESC, last.count DESC;

A query (que na verdade é duas) ordena o resultado por data, retorna o título vazio caso o post seja de mais de um mês atrás, junta as categorias (separando-as por vírgula) se um mesmo post for o último de mais de uma categoria e neste caso ordena as categorias por ordem decrescente de número de posts na mesma.

(A parte comentada seria para caso eu só quisesse mostrar o último post de categorias com três ou mais posts.)

Usei o resultado da query da seguinte forma:

$q = mysql_query($query); // $query é a string com aquele SQLzão
$print_final_li = false;
$first = true;
while ($a = mysql_fetch_array($q, MYSQL_ASSOC)) {
    echo "\t\t<li>";
    $permalink = get_permalink($a["id"]);
    $title = $a["title"];
    if ($title == "") {
        $print_final_li = true;
        echo "Veja também: ";
    } else if ($first == true) {
        echo "<a href="$permalink" title="$title">";
        echo get_the_post_thumbnail($a["id"], "home-thumbnail",
                                    Array("title" => get_the_title()));
        echo "</a> ";
        $first = false;
    }
    $cats = explode(",", $a["cat"]);
    foreach ($cats as $low) {
        $up = strtoupper($low);
        echo "<a class="cat" href="http://$low.juntos.org.br/"
                 title="Juntos! $up">$up</a> ";
    }
    if ($print_final_li) break;
    echo "<a class="post" href="$permalink" title="$title">$title</a>";
    echo "</li>\n";
}
while ($a = mysql_fetch_array($q, MYSQL_ASSOC)) {
    $cats = explode(",", $a["cat"]);
    foreach ($cats as $low) {
        $up = strtoupper($low);
        echo "<a class="cat" href="http://$low.juntos.org.br/"
                 title="Juntos! $up">$up</a> ";
    }
}
if ($print_final_li) {
    echo "</li>\n";
}

(e se você quiser vê-lo em prática, entre em juntos.org.br e procure por “Juntos pelo Brasil”)

Não ficou bonitinho? Se por um lado gostei da solução, por outro fiquei imaginando que deva ser um SQL tremendamente ingênuo e digno da minha inexperiência com grandes bancos de dados. O que você acha? Consegue pensar numa forma mais simples, mais eficiente e mais elegante de resolver o mesmo problema? Ou ao menos sem subqueries?

Acho que as relações necessárias já ficaram explícitas na query que eu escrevi, mas segue o diagrama do banco de dados do WordPress pra quem precisar:

Userscript para remover usuários de grupos do Facebook

ATENÇÃO: Este conteúdo foi publicado há 5 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

Já faz algum tempo que o Facebook tem um bug nas listas de membros dos grupos. As listas não mostram todos os membros do grupo. Mais: os membros que não aparecem na lista nem mesmo podem ser encontrados pelo formulário de busca de nomes. Quando você é administrador de um grupo, isso pode ser um grande inconveniente, porque na interface padrão do Facebook a lista de membros é o único lugar onde é possível excluir usuários de um grupo. Ou seja, os usuários que não aparecem lá são indeletáveis!


Só ali na lista de membros aparece esse X para excluir os membros do grupo.

Há alguns meses, escrevi um user script para o GreaseMonkey (extensão para o Firefox que permite que você crie esses pequenos scripts para serem rodados em páginas específicas) que busca ajudar os administradores de grupos a removerem esses membros fantasmas.

Não havia publicado até agora por vergonha (o script é bem feio, tanto o código como o resultado). Porém, acho que não pretendo perfumá-lo tão cedo, então resolvi liberá-lo aqui até pra inspirar quem quiser fazer coisas mais bonitas.

O funcionamento dele é muito simples: quando você entra num grupo (sendo ou não administrador — porque nem distingui isso no código), ele cria botões X do lado dos links para os perfis dos usuários que estão nessa página (e só nesses — outra coisa que nem me preocupei no script). Se você for administrador do grupo em questão, quando clica no X o Facebook abre aquela caixa perguntando se você quer mesmo excluir o membro em questão (e se quer baní-lo permanentemente).


Com o script, tem X em todo o lugar! (até onde não deve… hehe)

Sem mais enrolação, eis aqui o código para (des-)apreciação e aprimoramentos (por favor! :)):

// ==UserScript==
// @name Remove users from Facebook groups
// @description Remove usuarios que postam no grupo sem precisar procurar na lista de membros
// @author  Tiago Madeira <madeira@juntos.org.br>
// @include http*://www.facebook.com/groups/*
// @version 1.32
// ==/UserScript==
 
(function(){
    var as = document.getElementsByTagName("a");
    var gid = "";
 
    for (var i = 0; i < as.length; i++) {
        var a = as[i];
        var hovercard = a.getAttribute("data-hovercard");
        if (hovercard != null && hovercard != "") {
            uid = hovercard.replace(/.*id=/, '');
            if (gid != "") {
                var button = '<a class="mhm auxiliaryButton closeButton uiCloseButton" title="Remove" rel="dialog-post" href="/ajax/groups/members/remove.php?group_id=' + gid + '&uid=' + uid + '"></a>';
                a.innerHTML = a.innerHTML + " " + button;
            }
        }
        var pattern = new RegExp("(^| )groupsCleanProfilePic( |$)");
        if (pattern.test(a.className)) {
            gid = a.getAttribute("href").replace(/.*id=/, '');
        }
    }
})();

Ou o link direto para download (ou instalação no GreaseMonkey):

fb.group.remove.user.js (1.08kb)

Triângulo de Pascal mod m

ATENÇÃO: Este conteúdo foi publicado há 6 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

Este post possui intencionalmente apenas imagens e códigos.

Uso do programa que gera triângulo de Pascal mod m

mod 2

Pascal's Triangle mod 2

mod 3

Pascal's Triangle mod 3

mod 5

Pascal's Triangle mod 5

mod 7

Pascal's Triangle mod 7

mod 12

Pascal's Triangle mod 12

mod 23

Pascal's Triangle mod 23

Código-fonte (com alguns bugs inofensivos — procure XXX)

/* pascal -- generate colored Pascal's triangles in XPM format
 
   Copyright (C) 2011 Tiago Madeira <madeira@ime.usp.br>
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You can read a copy of the GNU General Public License at
   http://www.gnu.org/licenses/gpl-3.0.txt */
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <math.h>
 
#define DEFMOD 2
#define DEFSIZE 300
#define DEFPADDING 8
 
int mod = DEFMOD;
int size = DEFSIZE;
int padding = DEFPADDING;
 
char makecolors[6][4] = {
	"001", "010", "100", "110", "101", "001"
};
 
struct option longopts[] = {
	{"help", 0, 0, 'h'},
	{"padding", 1, 0, 'p'},
	{"size", 1, 0, 's'},
	{"mod", 1, 0, 'm'},
	{0, 0, 0, 0}
};
 
char *program_name;
 
void try_help() {
	fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
}
 
void help() {
	printf("Usage: %s [OPTION]... [FILE]\n", program_name);
	printf("Generate colored Pascal's triangles (XPM format).\n\n");
	printf("Mandatory arguments to long options are mandatory for short options too.\n\n");
	printf("  -h, --help        print this help\n");
	printf("  -m, --mod=M       paint with different colors numbers mod M\n");
	printf("  -p, --padding=SZ  image padding (margin) in pixels\n\n");
	printf("  -s, --size=SZ     generate SZ lines of Pascal's triangle\n\n");
	printf("With no FILE, or when FILE is -, write to standard output.\n\n");
	printf("Report bugs to <madeira@ime.usp.br>.\n");
}
 
int baselog(int n, int base) {
	return ceil(log(n) / log(base));
}
 
void *xmalloc(size_t size) {
	void *x = malloc(size);
	if (x == NULL) {
		fprintf(stderr, "There is no enough memory to allocate.\n");
		exit(3);
	}
	return x;
}
 
int main(int argc, char **argv) {
	int optc, tofile;
	int i, j;
	int width, height, chars;
	char **color, rgb[7];
	int one, pos;
	int *pascal;
	char *line;
 
	program_name = argv[0];
	while ((optc = getopt_long(argc, argv, "hm:p:s:", longopts, (int *)0)) != -1) {
		switch (optc) {
		case 'h':
			help();
			return 0;
		case 'm':
			mod = atoi(optarg);
			if (mod > 48) { /* XXX */
				fprintf(stderr, "At the moment, this program supports only mod <= 48 (color generation limit).\n");
				return 4;
			}
			if (mod > 26) { /* XXX */
				fprintf(stderr, "At the moment, this program supports only mod <= 26 (bad implementation limit).\n");
				return 5;
			}
			break;
		case 'p':
			padding = atoi(optarg);
			break;
		case 's':
			size = atoi(optarg);
			break;
		default:
			try_help();
			return 1;
		}
	}
	if (optind < argc && strcmp("-", argv[optind])) {
		if (freopen(argv[optind], "w", stdout) == NULL) {
			fprintf(stderr, "Can't open `%s' for writing.\n", argv[optind]);
			return 2;
		}
		tofile = 1;
	} else {
		tofile = 0;
	}
 
	printf("static char *a_xpm[] = {\n");
	width = size * 2 + padding * 2;
	height = size * 2 + padding * 2;
	chars = baselog(mod, 26);
	printf(""%d %d %d %d",\n", width, height, mod + 1, chars);
	color = xmalloc(sizeof(color[0]) * (mod+1));
	rgb[6] = '�';
 
	printf("\"");
	color[mod] = xmalloc(sizeof(color[mod][0]) * (chars + 1));
	for (j = 0; j < chars; j++) {
		color[mod][j] = ' ';
	}
	color[mod][chars] = '�';
	printf("%s c #000000\",\n", color[mod]);
 
	for (i = 0; i < mod; i++) {
		color[i] = xmalloc(sizeof(color[i][0]) * (chars + 1));
		if (i == 0) {
			for (j = 0; j < chars; j++) {
				color[i][j] = 'a';
			}
			color[i][chars] = '�';
		} else {
			strcpy(color[i], color[i-1]);
			for (j = chars-1; j >= 0; j--) {
				if (color[i][j] == 'z') {
					color[i][j] = 'a';
				} else {
					color[i][j]++;
					break;
				}
			}
		}
		one = 255 / pow(2, i / 6);
		sprintf(rgb, "%02x%02x%02x", makecolors[i%6][0] == '1' ? one : 0,
				makecolors[i%6][1] == '1' ? one : 0, makecolors[i%6][2] == '1' ? one : 0);
		printf("\"%s c #%s\",\n", color[i], rgb);
	}
 
	line = xmalloc(sizeof(line[0]) * (width+1));
	pascal = xmalloc(sizeof(pascal[0]) * size);
 
	line[width] = '�';
	for (j = 0; j < width; j++) {
		line[j] = ' ';
	}
	for (i = 0; i < padding; i++) {
		printf("\"%s\",\n", line);
	}
 
	memset(pascal, 0, sizeof(pascal[0]) * size);
	pascal[0] = 1;
 
	for (i = 0; i < size; i++) {
		for (j = i; j >= 0; j--) {
			if (j != 0) {
				pascal[j] = (pascal[j-1] + pascal[j]) % mod;
			}
			pos = padding + 2*j + (size - 1 - i);
			/* XXX a implementacao de line ficou toda errada e so estou pegando
			 * o primeiro caractere de color aqui. precisa ser reescrito. */
			line[pos] = line[pos+1] = *color[pascal[j]];
		}
		printf("\"%s\",\n\"%s\"%s\n", line, line, i == size-1 && !padding ? "" : ",");
	}
 
	line[width] = '�';
	for (j = 0; j < width; j++) {
		line[j] = ' ';
	}
	for (i = 0; i < padding; i++) {
		printf("\"%s\"%s\n", line, i == padding-1 ? "" : ",");
	}
	printf("};\n");
 
	if (tofile) {
		fclose(stdout);
	}
	return 0;
}

(Download — 5kb)

Calendário santo-discordiano no Fluxbox

ATENÇÃO: Este conteúdo foi publicado há 7 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

Acabei de implementar o calendário santo-discordiano no Fluxbox 1.1.1 (última versão).

Link para download do código: fluxbox-ddate-1.1.1-0.tar.bz2

Se você usa Arch Linux, não precisa baixar e compilar manualmente. Basta usar o pkgbuild que o Rev. Beraldo fez para o AUR: aur.archlinux.org/packages.php?ID=40364.

Se você usa Gentoo, não precisa baixar e compilar manualmente. Basta colocar o ebuild que eu fiz num overlay: fluxbox-ddate no Gentoo.

Versões compiladas .deb, .rpm, .tgz etc. e ebuilds, pkgbuilds etc. são bem vindos! Me passem que eu coloco um link aqui!

Como baixar e descompactar

Como você faria com qualquer outro pacote .tar.bz2…

$ wget http://tiagomadeira.com/wp-content/uploads/2010/08/fluxbox-ddate-1.1.1-0.tar.bz2
$ tar xjvf fluxbox-ddate-1.1.1-0.tar.bz2
$ cd fluxbox-ddate-1.1.1-0

Como compilar

Versão simples:

$ ./configure
$ make
$ make install

Versão complicada:

$ export CFLAGS=-O2 -march=native -msse4.1
$ export CXXFLAGS=$CFLAGS
$ ./configure --prefix=/usr --build=x86_64-pc-linux-gnu --host=x86_64-pc-linux-gnu --enable-nls --disable-xinerama --enable-xft --disable-gnome --enable-imlib2 --enable-slit --enable-toolbar --sysconfdir=/etc/X11/fluxbox
$ make -j3
$ make install

Encontre seu meio termo (ou use a simples) e divirta-se!

Como iniciar um Fluxbox

Inicie o X e peça pra ele abrir a versão que você compilou do Fluxbox da seguinte maneira:

$ startx /usr/local/bin/fluxbox -- :1

(Lembre-se de mudar /usr/local para o --prefix que usou no ./configure)

Esta linha funciona dentro de uma sessão do X (abre outra), por causa do -- :1.

Como usar a data discordiana

Clique com a tecla direita no relógio do seu Fluxbox e Edit clock format. Se você usar um formato de data convencional, do falso calendário (como deve estar usando no momento), nada de especial acontecerá. O segredo está no |fnord|.

Quando você coloca um |fnord| no formato, o Fluxbox interpretará tudo que veio antes como formato de data discordiano.

(Para escolher o formato de data ideal, você pode digitar man ddate num terminal.)

Exemplos de uso:

  • Formato: %c → Saída: Sat 28 Aug 2010 11:50:26 PM BRT
  • Formato: %e of %B of %Y|fnord| → Saída: 21st of Bureaucracy of 3176
  • Formato: %d %b %Y|fnord|, %H:%M:%S → Saída: 21 Bcy 3176, 23:50:26
  • Formato: %.|fnord| → Saída: All Hail Discordia!

Dúvidas, sugestões?

Contate a glândula pineal.

Como funciona o código?

O Fluxbox usa a função strftime para formatar a data do relógio. Isso acontece na linha 274 do arquivo src/ClockTool.cc. Modifiquei este trecho do código adicionando cerca de 23 linhas que separam a string do formato de data no |fnord| e passam o que vem antes dele como parâmetro para uma chamada de sistema pro ddate (sim, de fato pra próxima versão é melhor copiar o código do ddate ou reimplementar pra não ter este overhead) e o que vem depois continua indo pro strftime.

Ficou assim:

char s[255], u[255];
strcpy(s, m_timeformat->c_str());
char *t = strstr(s, "|fnord|");
time_string_len = 0;
if (t != NULL) {
	*t = '�';
	sprintf(u, "ddate +'%s'", s);
	FILE *ddate = popen(u, "r");
	if (fgets(time_string, 255, ddate)) {
		time_string_len = strlen(time_string);
		time_string[--time_string_len] = '�';
		fclose(ddate);
	}
	t+= 7;
} else {
	t = s;
}
time_string_len+= strftime(&time_string[time_string_len], 255 - time_string_len, t, time_type);

Known bugs

  1. Colocar ‘ (aspas simples) no lado esquerdo do |fnord| faz com que a data discordiana não apareça.
  2. Requer util-linux-ng e faz uma chamada de sistema ao ddate uma vez por segundo.
  3. Não trabalha ainda com horas métricas.
  4. … me informe se achar mais algum!

Screenshots

Screenshot 0

Screenshot 1

Screenshot 2

Gerando tabelas de hash MD5

ATENÇÃO: Este conteúdo foi publicado há 9 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

A idéia veio do nada. Na verdade, eu estava fazendo um freelance e procurando uma função pra “ordenar” um vetor aleatoriamente no PHP (no fim usei usort, que recebe uma função de comparação tipo o qsort do C) e me deparei com um comentário lá no PHP.net de um cara que usou geração de números aleatórios para criar um algoritmo de criptografia pra usar no lugar de MD5 e SHA, porque ele não confiava mais no MD5 e no SHA porque segundo ele existem tabelas na internet, o que torna um sistema muito vulnerável (para quem tem acesso ao banco de dados).

Procurei na internet e na verdade eu não encontrei muitos bancos de dados com vários MD5 não, ao menos não visíveis no Google quando se procura por um hash. Só pra strings como “1234”, mas não achei nem pra “060790” (minha data de nascimento). Veja você mesmo: 81811b48cc07432fc550cb42d4ab3e8f

Então pensei: são 31 dias por mês, 12 meses por ano, 100 anos considerando que os anos são representados por dois dígitos. Isso me dá 31 x 12 x 100 = apenas 37200 hashes. O custo pra gerar isso deve ser minúsculo, por que ninguém faz?

A idéia de fazer com essas datas de nascimento veio do fato de vários usuários leigos que conheço utilizarem data de nascimento pra suas senhas (muito também pelo fato de eles usarem essa mesma senha nas senhas de seis dígitos numéricos do banco e esse tipo de coisa). Então lá fui eu pro código. A princípio escrevi em cerca de 30 segundos – 1 minuto o seguinte código em Ruby:

require "md5"
 
100.times do |ano|
        12.times do |j|
                mes = j+1

                31.times do |i|
                        dia = i+1
                        string = sprintf("%02d%02d%02d", dia, mes, ano)

                        md5 = MD5.new(string).to_s
                        puts "#{string}: #{md5}"
                end

        end
end

É funcional e até eficiente…

tiago@flick ~ $ time ruby genmd5.rb > hashes-ruby

real    0m1.662s
user    0m0.620s
sys     0m0.028s

Mas minha geekialidade não permitiu que eu parasse por aqui. Nesse momento eu já nem me lembrava do freelance que estava fazendo e resolvi ver como usar MD5 em C. Meu primeiro chute foi um man md5 no terminal, que já me retornou a resposta da vida, do universo e tudo mais: o cabeçalho openssl/md5.h e sua função MD5:

unsigned char *MD5(const unsigned char *d, unsigned long n,
                 unsigned char *md);

Então lá fui eu pro programa:

#include <stdio.h>
#include <openssl/md5.h>
 
int main() {

        int dia, mes, ano;
        unsigned char string[STRING_LENGTH], md[MD5_DIGEST_LENGTH];
        for (ano = 0; ano < 100; ano++) {

                for (mes = 0; mes < 12; mes++) {
                        for (dia = 0; dia < 31; dia++) {

                                sprintf(string, "%02d%02d%02d", dia+1, mes+1, ano);
                                printf("%sn", MD5(string, sizeof(string), md));
                        }

                }
        }
        return 0;
}

Ao rodar, recebi saídas com caracteres estranhos e nenhum resultado visível. Corri pro Google. Procurei, procurei, e NINGUÉM usa essa maldita função MD5 do C num programa simples e não há exemplos nem howto de como utilizá-la. Depois de uns 30 minutos quebrando a cabeça (pra mais), percebi que alguns códigos que usavam isso na internet usavam %x (é o código pro printf imprimir inteiros hexadecimais) para imprimir caracteres do MD5 na tela. Aí encontrei algo assim:

printf("%x%x...%x%x", md[0], md[1], ..., md[14], md[15]);

(e pior que estou falando sério… tem gente na internet que não conhece for!)

E caiu a ficha. O MD5 tem 16 inteiros hexadecimais de um byte, 32 caracteres. Escrever o código abaixo me tomou bastante tempo, mas uma aprendizagem interessante e um ânimo pra voltar pro meu freelance:

#include <stdio.h>
#include <openssl/md5.h>
 
#define STRING_LENGTH 6
 
int main() {

        int dia, ano, mes;
        int i;
        unsigned char md[MD5_DIGEST_LENGTH], string[STRING_LENGTH];

 
        for (ano = 0; ano < 100; ano++) {
                for (mes = 0; mes < 12; mes++) {

                        for (dia = 0; dia < 31; dia++) {
                                sprintf(string, "%02d%02d%02d", dia+1, mes+1, ano);
                                MD5(string, sizeof(string), md);
                                printf("%s: ", string);
                                for (i = 0; i < 16; i++) {

                                        printf("%02x", md[i]); /* Note isso aqui! */
                                }
                                printf("n");
                        }

                }
        }
        return 0;
}

O “Note isso aqui!” ainda foi uma sacanagem incrível, o código que eu disse do cara que imprimia %x%x%x… não funciona na prática, porque quando um dos dígitos é 0X, ele só imprime X.

Enfim compilei meu código com -lssl (importante para C-zeiros de primeira viagem) e voi lá:

tiago@flick ~ $ time ./md5 > hashes-c

real    0m0.397s
user    0m0.136s
sys     0m0.028s

Confesso que é coisa de nerd mesmo querer otimizar um código que só serve pra gerar um arquivo de hashes e já o fez, mas no momento em que terminei esse teste, pensei: preciso postar isso no blog pra documentar o uso da função MD5 no C, antes que mais alguém perca o tempo que eu perdi.

Pra quem se pergunta se eu realmente fiz tudo certo:

tiago@flick ~ $ diff senhas-c senhas-ruby
tiago@flick ~ $ 

(ie. ou eu errei nos dois ou eu não errei em nenhum)

Útil, não? Não. Na verdade isso não serve pra absolutamente nada, a não ser que você roube o banco de dados de alguém e esse alguém usa como senha a data de nascimento da sua mãe. Mas aí ele merece mesmo que você pegue sua senha, então isso não muda nada.

Dá pra adaptar o código pra gerar outras tabelas, mas por favor não use isso pra nada maligno, e falo sério. Meus fins foram absolutamente educacionais/acadêmicos (eu queria aprender a trabalhar com a função MD5 da biblioteca OpenSSL, não dominar o mundo) e estou postando aqui para ajudar os que também vão querer usar essa função.

Comentários de crackers serão ignorados.

Feeds RSS para fotologs

ATENÇÃO: Este conteúdo foi publicado há 10 anos. Eu talvez nem concorde mais com ele. Se é um post sobre tecnologia, talvez não faça mais sentido. Mantenho neste blog o que escrevo desde os 14 anos por motivos históricos. Leia levando isso em conta.

Não tenho programado absolutamente nada, a não ser nos trabalhos remunerados. Eu vejo a programação como uma arte… Programar é como compôr uma música, pintar, atuar; é preciso haver inspiração. Eu não ando inspirado para programar, mas nesse caso a necessidade me levou a criação de um script que pode ter utilidade para algum leitor perdido…

No ano passado, eu escrevi um script que gerava feeds RSS para fotologs do fotolog.net. O tempo passou, o meu usuário na UNICAMP foi deletado e nessa semana eu voltei a ter a necessidade de acompanhar um fotolog. Então, resolvi reescrever o script, agora com mais funcionalidades:

  • Compatível com Bloglines
  • Tem como descrição o “título” que o usuário coloca no Fotolog.net.
  • É atualizado sempre que há novos comentários nos últimos 6 posts.
  • Você pode comentar de dentro do seu leitor de feeds (ele copia o formulário de comentário pro feed).
  • Ele faz cache de tudo.

Resolvi presentear todo mundo usando o presente que a Dreamhost me deu no mês passado: 200gb de espaço e 2tb de banda tinham que servir pra alguma coisa no meu servidor. Então, ao invés de disponibilizar o código, eu criei uma página PHP que gera o feed que você quiser para você:

http://fotolog.tiagomadeira.net/USUARIO

Para ver um feed em ação no Bloglines, entre no meu diretório de feeds e procure um feed com prefixo “fotolog.com/”.

Depois de acabar de criar o script, ainda lembrei que o IE7 foi lançado nessa semana com suporte a feeds! Então, esse negócio pode ser útil pra bastante gente.

Acompanhe os fotologs dos seus amigos no Bloglines, divulgue aos seus amigos, comente sugerindo melhorias; aproveite enquanto eu ainda estou inspirado pra corrigir os defeitos! =)