Simulated Woodtype Winamp2 skin fixed for Audacious 2.x

For many years I've been using a skin called "Dirty Simulated Woodtype". Long ago it was with Winamp 2.x on windows. When I moved to linux I began using XMMS, but eventually easy support for gtk1 applications, and the way XMMS interacted with ALSA and/or Pulseaudio became... impaired... in distributions I used. Wanting to stay with a small memory footprint, no nonsense music player I switched over to Audacious 2.3. Unfortunately Audacious isn't completely cross compatible with all Winamp style skins. So I unzipped the skin and spent a few hours trial and error editing bitmaps till the corruptions when away. This is it; the same old skin. But it now works in Audacious 2.x. I tried to contact the creator of this skin but all the email addresses I found just bounced. So I'm hosting it here instead. The .wsz file is just a renamed zip containing the bitmaps. It should load fine in any app supporting winamp skins, and, of course, Audacious.

Simulated Woodtype Winamp2 skin fixed for Audacious 2.x

[comment on this post] Append "/@say/your message here" to the URL in the location bar and hit enter.

[webmention/pingback] Did you respond to this URL? What's your URL?



2026: vibe coding a .c scrobbling plugin for Audacious 2.3 on Ubuntu 10.04

The old plugin wasn't working with modern libre.fm scrobbling and since it is the future now I decided to vibe code a simple scrobbling plugin instead of trying to fix the old one. This is the result. It requires libcurl and glib against ancient ubuntu 10.04 audacious-dev libraries. I doubt anyone would use this besides me in 2026, but just in case and to keep this page accurate, here's what I use: librefm-scrobber.c. And you should never use this, compile it yourself, but: scrobbler.so

Build it like,

gcc -shared -fPIC -o scrobbler.so librefm-scrobbler.c \
       $(pkg-config --cflags --libs audacious glib-2.0) \
       -lcurl -lpthread

Then create a config file at ~/.config/audacious/librefm-scrobbler.conf with your username and password,

username=YOUR_LIBREFM_USERNAME
password_plain=YOUR_PASSWORD
-- OR --
password=PRECOMPUTED_MD5_HEX 

The libre.fm scrobbling URL is baked into the code. In order to change scrobbling services you have edit,

#define HS_URL        "https://turtle.libre.fm/"

and then the cosmetic console output,

    fprintf(stderr, PLUGIN_NAME ": handshaking with turtle.libre.fm ...\n");

and then recompile and re-copy the new scrobbler.so file into /usr/lib/audacious/General/

/*
 * librefm-scrobbler.c
 * Audacious 2.x General Plugin: scrobbles to libre.fm via AudioScrobbler 1.2
 *
 * Tested against Audacious 2.3 on Ubuntu 10.04 (audacious-dev package).
 *
 * BUILD:
 *   gcc -shared -fPIC -o scrobbler.so librefm-scrobbler.c \
 *       $(pkg-config --cflags --libs audacious glib-2.0) \
 *       -lcurl -lpthread
 *
 * INSTALL:
 *   sudo cp scrobbler.so /usr/lib/audacious/General/
 *
 * CONFIG:  ~/.config/audacious/librefm-scrobbler.conf
 *   username=YOUR_LIBREFM_USERNAME
 *   password_plain=YOUR_PASSWORD        (plugin MD5s it at startup)
 *   -- OR --
 *   password=PRECOMPUTED_MD5_HEX        (echo -n 'pass' | md5sum)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>

#include <glib.h>
#include <curl/curl.h>

/*
 * plugin.h itself does: #include "libaudcore/tuple.h"
 * so including it gives us Tuple, FIELD_*, tuple_get_string, tuple_get_int.
 * All the aud_* macros we use expand to the real function names via plugin.h.
 *
 * Confirmed API (from header grep):
 *   aud_playlist_get_active()                         -> gint
 *   aud_playlist_get_position(gint playlist)          -> gint
 *   playlist_entry_get_tuple(gint pl, gint entry)     -> const Tuple *
 *   tuple_get_string(const Tuple *, gint field, NULL) -> const gchar *
 *   tuple_get_int(const Tuple *, gint field, NULL)    -> gint
 *   aud_tuple_free(void *)   == mowgli_object_unref
 *   aud_drct_get_length()                             -> gint (ms)
 *   DECLARE_PLUGIN(name, init, fini, ip, op, ep, gp, vp, interface)
 */
#include <audacious/plugin.h>

/* ================================================================
 * Constants
 * ================================================================ */
#define PLUGIN_NAME   "Libre.fm Scrobbler"
#define CLIENT_ID     "aud"
#define CLIENT_VER    "1"
#define HS_URL        "https://turtle.libre.fm/"
#define CONFIG_FILE   "librefm-scrobbler.conf"
#define MIN_PLAY_SEC  240
#define MIN_TRACK_SEC  30

/* 
MIN_TRACK_SEC 30 — a track must be at least 30 seconds long to be eligible at all. Anything shorter (jingles, intros, sound effects) is simply never scrobbled regardless of how long you listen.
MIN_PLAY_SEC 240 — this is not "the track must be 4 minutes long". It's "you must have played 240 seconds of it". It's one of two ways a scrobble can qualify. The two conditions are OR'd together:
You've been playing it for 240 seconds or more, OR
You've been playing it for at least half its total length
*/

/* ================================================================
 * State
 * ================================================================ */
static pthread_mutex_t mtx     = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cond    = PTHREAD_COND_INITIALIZER;
static pthread_t       worker;
static gboolean        running = FALSE;

static char session_id[256] = "";
static char np_url[512]     = "";
static char sub_url[512]    = "";
static time_t session_expiry = 0;

static char cfg_user[256] = "";
static char cfg_pass[64]  = "";   /* MD5 hex of password */

typedef struct {
    char     artist[512];
    char     title[512];
    char     album[256];
    int      length;    /* seconds, -1 = unknown */
    int      trackno;
    time_t   started;
    gboolean valid;
} TrackInfo;

static TrackInfo cur  = { "", "", "", -1, -1, 0, FALSE };
static TrackInfo pend = { "", "", "", -1, -1, 0, FALSE };
static gboolean  want_np  = FALSE;
static gboolean  want_sub = FALSE;

/* ================================================================
 * MD5 via GLib (available in Ubuntu 10.04's glib 2.22)
 * ================================================================ */
static void md5hex(const char *s, char out[33])
{
    gchar *h = g_compute_checksum_for_string(G_CHECKSUM_MD5, s, -1);
    if (h) { g_strlcpy(out, h, 33); g_free(h); }
    else     out[0] = '\0';
}

/* ================================================================
 * Config
 * ================================================================ */
static void load_config(void)
{
    gchar *path = g_build_filename(g_get_home_dir(), ".config",
                                   "audacious", CONFIG_FILE, NULL);
    FILE *f = fopen(path, "r");
    g_free(path);
    if (!f) {
        g_warning(PLUGIN_NAME ": no config (~/.config/audacious/%s)", CONFIG_FILE);
        return;
    }
    char line[1024];
    while (fgets(line, sizeof(line), f)) {
        line[strcspn(line, "\r\n")] = '\0';
        char *eq = strchr(line, '=');
        if (!eq) continue;
        *eq = '\0';
        const char *k = line, *v = eq + 1;
        if      (!strcmp(k, "username"))       g_strlcpy(cfg_user, v, sizeof(cfg_user));
        else if (!strcmp(k, "password"))       g_strlcpy(cfg_pass, v, sizeof(cfg_pass));
        else if (!strcmp(k, "password_plain")) md5hex(v, cfg_pass);
    }
    fclose(f);
}

/* ================================================================
 * CURL helpers
 * ================================================================ */
typedef struct { char *buf; size_t len; } Cbuf;

static size_t on_data(void *ptr, size_t size, size_t n, void *ud)
{
    size_t total = size * n;
    Cbuf *b = (Cbuf *)ud;
    b->buf = g_realloc(b->buf, b->len + total + 1);
    memcpy(b->buf + b->len, ptr, total);
    b->len += total;
    b->buf[b->len] = '\0';
    return total;
}

static char *do_get(const char *url)
{
    Cbuf b = {NULL, 0};
    CURL *c = curl_easy_init();
    if (!c) return NULL;
    curl_easy_setopt(c, CURLOPT_URL,           url);
    curl_easy_setopt(c, CURLOPT_CAINFO,        "/etc/ssl/certs/ca-certificates.crt");
    curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, on_data);
    curl_easy_setopt(c, CURLOPT_WRITEDATA,     &b);
    curl_easy_setopt(c, CURLOPT_TIMEOUT,       30L);
    curl_easy_setopt(c, CURLOPT_USERAGENT,     PLUGIN_NAME "/" CLIENT_VER);
    CURLcode r = curl_easy_perform(c);
    curl_easy_cleanup(c);
    if (r != CURLE_OK) { g_free(b.buf); return NULL; }
    return b.buf;
}

static char *do_post(const char *url, const char *fields)
{
    Cbuf b = {NULL, 0};
    CURL *c = curl_easy_init();
    if (!c) return NULL;
    curl_easy_setopt(c, CURLOPT_URL,           url);
    curl_easy_setopt(c, CURLOPT_CAINFO,        "/etc/ssl/certs/ca-certificates.crt");
    curl_easy_setopt(c, CURLOPT_POSTFIELDS,    fields);
    curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, on_data);
    curl_easy_setopt(c, CURLOPT_WRITEDATA,     &b);
    curl_easy_setopt(c, CURLOPT_TIMEOUT,       30L);
    curl_easy_setopt(c, CURLOPT_USERAGENT,     PLUGIN_NAME "/" CLIENT_VER);
    CURLcode r = curl_easy_perform(c);
    curl_easy_cleanup(c);
    if (r != CURLE_OK) { g_free(b.buf); return NULL; }
    return b.buf;
}

static char *urlencode(const char *s)
{
    CURL *c = curl_easy_init();
    if (!c) return g_strdup(s);
    char *e   = curl_easy_escape(c, s, 0);
    char *ret = g_strdup(e ? e : s);
    curl_free(e);
    curl_easy_cleanup(c);
    return ret;
}

/* ================================================================
 * Handshake
 * AudioScrobbler 1.2:
 *   GET http://turtle.libre.fm/?hs=true&p=1.2&c=CLIENT&v=VER&u=USER&t=TS&a=TOKEN
 *   TOKEN = md5( md5(password) + timestamp_string )
 * Response: OK\n<session>\n<np_url>\n<sub_url>\n
 * ================================================================ */
static gboolean do_handshake(void)
{
    if (!cfg_user[0] || !cfg_pass[0]) {
        g_warning(PLUGIN_NAME ": no credentials – edit ~/.config/audacious/%s",
                  CONFIG_FILE);
        return FALSE;
    }

    char ts_str[32];
    snprintf(ts_str, sizeof(ts_str), "%ld", (long)time(NULL));

    char tmp[128];
    snprintf(tmp, sizeof(tmp), "%s%s", cfg_pass, ts_str);
    char token[33];
    md5hex(tmp, token);

    char *ue  = urlencode(cfg_user);
    char *url = g_strdup_printf("%s?hs=true&p=1.2&c=%s&v=%s&u=%s&t=%s&a=%s",
                                HS_URL, CLIENT_ID, CLIENT_VER, ue, ts_str, token);
    g_free(ue);

    fprintf(stderr, PLUGIN_NAME ": handshaking with turtle.libre.fm ...\n");
    fflush(stderr);
    char *resp = do_get(url);
    g_free(url);
    if (!resp) { fprintf(stderr, PLUGIN_NAME ": handshake: no response\n"); fflush(stderr); return FALSE; }

    gchar **lines = g_strsplit(resp, "\n", 0);
    g_free(resp);
    gboolean ok = FALSE;
    if (lines && lines[0]) {
        if (g_str_has_prefix(lines[0], "OK") &&
                lines[1] && lines[2] && lines[3]) {
            g_strlcpy(session_id, g_strstrip(lines[1]), sizeof(session_id));
            g_strlcpy(np_url,     g_strstrip(lines[2]), sizeof(np_url));
            g_strlcpy(sub_url,    g_strstrip(lines[3]), sizeof(sub_url));
            session_expiry = time(NULL) + 3600;
            fprintf(stderr, PLUGIN_NAME ": session OK\n"); fflush(stderr);
            ok = TRUE;
        } else {
            fprintf(stderr, PLUGIN_NAME ": handshake error: %s\n", lines[0]); fflush(stderr);
        }
    }
    g_strfreev(lines);
    return ok;
}

static gboolean ensure_session(void)
{
    if (session_id[0] && time(NULL) < session_expiry) return TRUE;
    session_id[0] = np_url[0] = sub_url[0] = '\0';
    session_expiry = 0;
    return do_handshake();
}

/* ================================================================
 * Now Playing
 * ================================================================ */
static void send_nowplaying(const TrackInfo *t)
{
    if (!ensure_session()) return;
    char *a  = urlencode(t->artist);
    char *ti = urlencode(t->title);
    char *b  = urlencode(t->album);
    char len[16] = "", trk[16] = "";
    if (t->length  > 0) snprintf(len, sizeof(len), "%d", t->length);
    if (t->trackno > 0) snprintf(trk, sizeof(trk), "%d", t->trackno);
    char *le = urlencode(len), *te = urlencode(trk);
    char *post = g_strdup_printf("s=%s&a=%s&t=%s&b=%s&l=%s&n=%s&m=",
                                 session_id, a, ti, b, le, te);
    g_free(a); g_free(ti); g_free(b); g_free(le); g_free(te);
    fprintf(stderr, PLUGIN_NAME ": now playing: %s - %s\n", t->artist, t->title); fflush(stderr);
    char *resp = do_post(np_url, post);
    g_free(post);
    if (resp) {
        if (!g_str_has_prefix(resp, "OK")) {
            fprintf(stderr, PLUGIN_NAME ": now-playing response: %s\n", resp); fflush(stderr);
        }
        if (g_str_has_prefix(resp, "BADSESSION")) session_expiry = 0;
        g_free(resp);
    }
}

/* ================================================================
 * Scrobble submit
 * ================================================================ */
static void submit_scrobble(const TrackInfo *t)
{
    if (!ensure_session()) return;
    char ts[32];
    snprintf(ts, sizeof(ts), "%ld", (long)t->started);
    char *a   = urlencode(t->artist);
    char *ti  = urlencode(t->title);
    char *b   = urlencode(t->album);
    char *tse = urlencode(ts);
    char len[16] = "", trk[16] = "";
    if (t->length  > 0) snprintf(len, sizeof(len), "%d", t->length);
    if (t->trackno > 0) snprintf(trk, sizeof(trk), "%d", t->trackno);
    char *le  = urlencode(len);
    char *tre = urlencode(trk);
    char *post = g_strdup_printf(
        "s=%s&a[0]=%s&t[0]=%s&i[0]=%s&o[0]=P&r[0]=&l[0]=%s&b[0]=%s&n[0]=%s&m[0]=",
        session_id, a, ti, tse, le, b, tre);
    g_free(a); g_free(ti); g_free(b); g_free(tse); g_free(le); g_free(tre);
    fprintf(stderr, PLUGIN_NAME ": scrobbling: %s - %s\n", t->artist, t->title); fflush(stderr);
    char *resp = do_post(sub_url, post);
    g_free(post);
    if (resp) {
        if (g_str_has_prefix(resp, "OK")) {
            fprintf(stderr, PLUGIN_NAME ": scrobble accepted\n"); fflush(stderr);
        } else {
            fprintf(stderr, PLUGIN_NAME ": submit response: %s\n", resp); fflush(stderr);
        }
        if (g_str_has_prefix(resp, "BADSESSION")) session_expiry = 0;
        g_free(resp);
    }
}

/* ================================================================
 * Worker thread
 * ================================================================ */
static void *worker_func(void *unused)
{
    (void)unused;
    pthread_mutex_lock(&mtx);
    while (running) {
        while (running && !want_np && !want_sub)
            pthread_cond_wait(&cond, &mtx);
        if (!running) break;
        if (want_sub) {
            TrackInfo t = pend; want_sub = FALSE;
            pthread_mutex_unlock(&mtx);
            submit_scrobble(&t);
            pthread_mutex_lock(&mtx);
        }
        if (want_np) {
            TrackInfo t = cur; want_np = FALSE;
            pthread_mutex_unlock(&mtx);
            send_nowplaying(&t);
            pthread_mutex_lock(&mtx);
        }
    }
    pthread_mutex_unlock(&mtx);
    return NULL;
}

/* ================================================================
 * Metadata from Audacious 2.x
 *
 * Confirmed from header grep:
 *   aud_playlist_get_active()                     -> gint
 *   aud_playlist_get_position(gint pl)            -> gint
 *   playlist_entry_get_tuple(gint pl, gint entry) -> const Tuple *
 *   tuple_get_string(const Tuple *, gint, NULL)   -> const gchar *
 *   tuple_get_int(const Tuple *, gint, NULL)      -> gint
 *   aud_tuple_free(ptr)  == mowgli_object_unref
 *   aud_drct_get_length()                         -> gint ms
 *
 * FIELD_* constants come from libaudcore/tuple.h which plugin.h includes.
 * ================================================================ */
static void fill_track(TrackInfo *t)
{
    memset(t, 0, sizeof(*t));
    t->length = t->trackno = -1;
    t->valid  = FALSE;

    gint len_ms = audacious_drct_get_length();
    if (len_ms > 0) t->length = len_ms / 1000;

    gint    pl  = aud_playlist_get_active();
    gint    pos = aud_playlist_get_position(pl);
    Tuple  *tup = (Tuple *) aud_playlist_entry_get_tuple(pl, pos);
    if (!tup) return;

    const gchar *artist  = tuple_get_string(tup, FIELD_ARTIST,       NULL);
    const gchar *title   = tuple_get_string(tup, FIELD_TITLE,        NULL);
    const gchar *album   = tuple_get_string(tup, FIELD_ALBUM,        NULL);
    gint         trackno = tuple_get_int   (tup, FIELD_TRACK_NUMBER, NULL);

    if (artist) g_strlcpy(t->artist, artist, sizeof(t->artist));
    if (title)  g_strlcpy(t->title,  title,  sizeof(t->title));
    if (album)  g_strlcpy(t->album,  album,  sizeof(t->album));
    if (trackno > 0) t->trackno = trackno;

    /* Do not free the tuple - we don't own it in Audacious 2.x */

    t->valid = (t->artist[0] != '\0' && t->title[0] != '\0');
}

/* ================================================================
 * Eligibility
 * ================================================================ */
static gboolean should_scrobble(const TrackInfo *t)
{
    if (!t->valid || t->started == 0) return FALSE;
    if (t->length > 0 && t->length < MIN_TRACK_SEC) return FALSE;
    time_t elapsed = time(NULL) - t->started;
    if (elapsed >= MIN_PLAY_SEC) return TRUE;
    if (t->length > 0 && elapsed >= t->length / 2) return TRUE;
    return FALSE;
}

/* ================================================================
 * Hook callbacks
 * ================================================================ */
static void on_playback_begin(gpointer hook_data, gpointer user_data)
{
    (void)hook_data; (void)user_data;
    pthread_mutex_lock(&mtx);
    if (should_scrobble(&cur)) { pend = cur; want_sub = TRUE; }
    fill_track(&cur);
    cur.started = time(NULL);
    if (cur.valid) { want_np = TRUE; pthread_cond_signal(&cond); }
    pthread_mutex_unlock(&mtx);
}

static void on_playback_stop(gpointer hook_data, gpointer user_data)
{
    (void)hook_data; (void)user_data;
    pthread_mutex_lock(&mtx);
    if (should_scrobble(&cur)) {
        pend = cur; want_sub = TRUE;
        pthread_cond_signal(&cond);
    }
    memset(&cur, 0, sizeof(cur));
    cur.length = cur.trackno = -1;
    pthread_mutex_unlock(&mtx);
}

/* ================================================================
 * Plugin init / cleanup  (void return in Audacious 2.x)
 * ================================================================ */
static void scrobbler_init(void)
{
    curl_global_init(CURL_GLOBAL_DEFAULT);
    load_config();
    running = TRUE;
    pthread_create(&worker, NULL, worker_func, NULL);
    aud_hook_associate("playback begin", on_playback_begin, NULL);
    aud_hook_associate("playback stop",  on_playback_stop,  NULL);
    aud_hook_associate("playback end",   on_playback_stop,  NULL);
    g_message(PLUGIN_NAME ": loaded (user: %s)",
              cfg_user[0] ? cfg_user : "unconfigured");
}

static void scrobbler_cleanup(void)
{
    aud_hook_dissociate("playback begin", on_playback_begin);
    aud_hook_dissociate("playback stop",  on_playback_stop);
    aud_hook_dissociate("playback end",   on_playback_stop);
    pthread_mutex_lock(&mtx);
    running = FALSE;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mtx);
    pthread_join(worker, NULL);
    curl_global_cleanup();
    g_message(PLUGIN_NAME ": unloaded");
}

/* ================================================================
 * Plugin registration
 *
 * From header grep, DECLARE_PLUGIN signature is:
 *   (name, init, fini, ip_list, op_list, ep_list, gp_list, vp_list, interface)
 * ================================================================ */
static GeneralPlugin gp_info = {
    .description = (gchar *) PLUGIN_NAME,
    .init        = scrobbler_init,
    .cleanup     = scrobbler_cleanup,
};

GeneralPlugin *gen_plugin_list[] = { &gp_info, NULL };

DECLARE_PLUGIN(librefm_scrobbler, NULL, NULL,
               NULL, NULL, NULL,
               gen_plugin_list,
               NULL, NULL);


How to compile the scrobbling plugin from Audacious 2.2 for Audacious 2.3 on Ubuntu 10.04

$ sudo apt-get install audacious-dev
$ sudo aptitude install libcurl4-dev
$ wget http://distfiles.atheme.org/audacious-plugins-2.2.tgz
$ tar -zxvf audacious-plugins-2.2.tgz
$ cd audacious-plugins-2.2

$ ./configure --enable-dependency-tracking --enable-lastfm --enable-scrobbler --disable-aac --disable-icecast --disable-adplug --disable-jack --disable-alsa --disable-altivec --disable-libFLACtest --disable-amidiplug --disable-libmadtest --disable-amidiplug-alsa --disable-lirc --disable-amidiplug-dummy --disable-mms --disable-amidiplug-flsyn --disable-modplug --disable-aosd --disable-mp3 --disable-aosd-xcomp --disable-mtp_up --disable-bluetooth --disable-neon --disable-bs2b --disable-nls --disable-cdaudio --disable-option-checking --disable-coreaudio --disable-oss --disable-cue --disable-paranormal --disable-dbus --disable-projectm --disable-dependency-tracking --disable-projectm-1.0 --disable-dockalbumart --disable-pulse --disable-esd --disable-rocklight --disable-evdevplug --disable-rpath --disable-ffaudio --disable-sid --disable-filewriter --disable-sndfile --disable-filewriter_flac --disable-sse2 --disable-filewriter_mp3 --disable-statusicon --disable-filewriter_vorbis --disable-streambrowser --disable-flacng --disable-vorbis --disable-gio --disable-wavpack --disable-gnomeshortcuts --disable-xmltest --disable-hotkey --disable-xspf

Use an editor to open (in audacious-plugins-2.2) configure (line 7498) and configure.ac (line 126),

change:

INPUT_PLUGINS="tonegen console psf xsf metronom vtx"
OUTPUT_PLUGINS="crossfade null"
EFFECT_PLUGINS="audiocompress crystalizer ladspa voice_removal sndstretch stereo_plugin echo_plugin"
GENERAL_PLUGINS="song_change alarm skins vfstrace gtkui"
VISUALIZATION_PLUGINS="blur_scope spectrum"
CONTAINER_PLUGINS="m3u pls"

to:

INPUT_PLUGINS=""
OUTPUT_PLUGINS=""
EFFECT_PLUGINS=""
GENERAL_PLUGINS="song_change"
VISUALIZATION_PLUGINS=""
CONTAINER_PLUGINS=""

Then,

$ make
$ cd src/scrobbler/
$ sudo cp scrobbler.so /usr/lib/audacious/General

Interests

Other

Good Books

Type, "/@say/Your message here." after the end of any URL on my site and hit enter to leave a comment. You can view them here. An example would be, http://superkuh.com/rtlsdr.html/@say/Your message here.

Member of The Internet Defense League

Legal Bullshit

DMCA Requests

Terms of Use:

You may not access or use the site superkuh.com if you are under 90 years of age. If you do not agree then you must leave now.

The US Dept. of Justice has determined that violating a website's terms of service is a felony under CFAA 1030(a)2(c). Under this same law I can declare that you may only use one IP address to access this site; circumvention is a felony. Absurd, isn't it?

It is my policy to regularly delete server logs. I don't log at all for the tor onion service.

bellcop.

Bullshit

search. (via google)

door.

HTTP+HTTPS is a legitimate strategy.