Tom Adler’s blog

shift-scroll для горизонтальной прокрутки

с некоторым удивлением я узнал, что в некоторых программах можно использовать колёсико мыши для горизонтальной прокрутки, удерживая Shift. Очень удобная фича, которую мне немедленно захотелось иметь в своём браузере. Сейчас я использую для этого Panning, который включается кликом средней кнопки, но у него есть свои недостатки. Во-первый, он довольно медленный (или слишком быстрый, если двигать мышь неаккуратно). Во-вторых, можно случайно кликнуть по ссылке или полю ввода, и получить неожиданный результат.

к сожалению, это сочетание кнопок в браузерах уже зарезервировано под переходы вперёд/назад по истории. К ещё большему сожалению, в опере это внезапно! нельзя отключить. И, наконец, в опере это событие нельзя предотвратить яваскриптом (DSK-351368). Так что пришлось использовать Alt, что тоже неплохо. А вот и код:

document.addEventListener(
  'mousewheel',
  function(event){
    if (!event.altKey) return;

    for (
      var target = event.target;
      target;
      target = target.parentNode)
    {
      if (target.scrollWidth <= target.offsetWidth) continue;
      target.scrollLeft -= event.wheelDelta;
      event.preventDefault();
      break;
    }
  },
  false);

,

imdb opensearch suggestions

кстати, если кто хочет себе в браузер вставить подсказки для поиска по imdb, то вот быстрый и грязный, но работающий урл:

http://imdb-opensearch-suggestions.appspot.com/?{searchTerms}

удобная всё-таки штука App Engine как раз вот для таких мелких общественно-полезных скриптов

import json
import urllib2, os

template = 'http://sg.media-imdb.com/suggests/%s/%s.json'

query = os.environ['QUERY_STRING']
url = template % (query[0], query[:5])

response = ''.join(urllib2.urlopen(url))
response = response[response.find('(')+1:-1]

data = json.loads(response)

print "Content-Type: application/json\n"
print json.dumps([query, map(lambda x: x['l'], data['d'])])

,

агент читателя

во многих разных фантастических книжках и фильмах компьютер персонажа действует самостоятельно, изучая данные в сети и отбирая из них то, что нужно его владельцу. Иными словами, компьютер там работает как доверенный агент человека, почти не обременённый интеллектом, но способный на самостоятельность. К сожалению или к счастью, в реальном мире обычные люди практически не используют компьютеры таким образом.

некоторое приближение к фантастике можно увидеть в том, как программы-аггрегаторы обходят сайты, на которые подписан владелец, и собирают с них обновления в виде atom/rss. Однако самостоятельности тут почти нет: я не знаю программ, которые хотя бы переходили по ссылкам в потоках, не говоря уже о ссылках второго уровня. Вроде бы есть аггрегаторы, которые могут показать не взятый из atom/rss текст, а страницу, на которую он ссылается, но обычно результат от этого становится только хуже: на странице практически всегда присутствует очень много лишних элементов. Хотя понятно, что таким образом они пытаются решить проблему «в atom/rss присутствует только анонс текста».

поскольку у меня есть браузер с широкими возможностями пользовательских скриптов и, по сути, собственная читалка atom/rss, ничто не помешало мне разобраться с этой проблемой любимым способом. Читалке я сказал, какие потоки содержат только анонсы. За один вечер я научил её открывать ссылки из этих потоков в ифреймах, вынимать из загруженных страниц интересующие меня теги (для каждого сайта — свой css-селектор), и показывать их прямо в интерфейсе читалки вместо анонсов. Получился примитивный агент, выбирающий для меня только нужный мне текст вопреки огораживанию

кстати, на очевидное возражение «сайтам тоже нужно зарабатывать» контраргумент настолько же очевиден: «компьютер должен служить своему хозяину»

,

замена рекомендаций в ридере

в новом интерфейсе ридера возможности рекомендовать записи уже нет, но в API она пока ещё работает. Однако вполне вероятно, что скоро её в ридере не будет вообще. Исполненный этих мрачных предчувствий, я решил построить свой «шаринг», с блекджеком WebDAV и incron.

выбор технологии обусловила моя нездоровая (?) любовь к статическим файлам, которые лежат себе мирно на диске и ведут себя предсказуемо, в отличие от некоторых. Например, скопировав себе всю историю своих рекомендаций, я разбил её на отдельные файлики, каждый из которых содержит один xml-элемент entry из потока, а имя файла соответствует названию записи. Ну и для удобства обращения с 11к файлов я разбил их на папки по месяцам, и установил временем модификации файла момент, когда я рекомендовал эту запись.

такой способ хранения прямо-таки напрашивается на создание потока простой конкатенацией. Нужно только как-то добавить очередной файл в нужный каталог и склеить двадцать последних файлов с префиксом. Для добавления можно использовать встроенную в модный nginx поддержку WebDAV, с которой мне давно хотелось поиграться. А запускать скрипт по изменению в файловой системе может ещё более модный incron.

к сожалению, на виртуальном сервере incron работал не идеально, поэтому на ровном месте пришлось хорошо побиться лбом о стену. Однако удалось добиться того, чтобы при загрузке файла в каталог или его удалении запускался нужный скрипт. Встроенная защита от рекурсии почему-то не работала, пришлось полагаться на старый добрый lock-файл. На всякий случай приведу здесь единственную строчку конфигурации incron:

inbox IN_DELETE,IN_MOVED_TO new-entry.bash

со скриптом тоже внезапно пришлось повозиться — «спасибо» башу за удобство работы с файлами, в имени которых есть пробелы. Вообще-то, сначала я собирался избежать этой проблемы, написав скрипт на питоне, но не хотелось возиться в нём с сортировкой файлов по времени создания. Хорошо, что задача довольно простая: скопировать самый свежий файл из «входящих» в правильный каталог на хранение, удалить самый старый файл во «входящих», подклеить к префиксу все файлы во «входящих»:

ls -t inbox | head -n 1 | xargs -I{} cp "inbox/{}" `date +"entries/%Y-%m/"`
ls -t inbox | tail -n 1 | xargs -I{} rm "inbox/{}"

cat prefix > index.tmp
echo "<updated>" `date --utc --rfc-3339=s | tr ' ' T` "</updated>" >> index.tmp
echo >> index.tmp
ls -t inbox | xargs -I{} cat "inbox/{}" >> index.tmp
echo "</feed>" >> index.tmp

rm index.atom
mv index.tmp index.atom

ну и ещё одни грабли ждали меня при попытке использовать WebDAV. Я помнил, что яваскрипт работает на www.google.com, а моё хранилище — на совсем другом домене, но собирался обойти кроссдоменное ограничение классической отправкой формы в ифрейм. Не тут-то было: обычные html-формы не умеют метод PUT, его можно использовать только в XMLHttpRequest — но он споткнётся о другой домен. Хорошо, что есть postMessage, и что я полностью контролирую свой сервер: маленький прокси меня выручил.

<!doctype html>
<title>proxy</title>
<script>
window.onmessage = function(event) {
    if (!event.origin.match(/^https?:..www.google.com/)) return;
    var url = encodeURIComponent(event.data.name.replace(/\//g, '-'));
    var x = new XMLHttpRequest();
    x.open('PUT', 'http://shared.arty.name/inbox/' + url);
    x.send(event.data.content);
}
</script>

в итоге, когда я нажимаю в ридере кнопку «рекомендовать», происходит вот что:

по-моему, здорово! : ) Сыровато ещё, нужны доделки, но уже работает, да как затейливо! : )

, , ,

export google reader shared posts

благодаря open google reader интерфейс google reader для меня не изменился, и возможность рекомендовать записи у меня тоже осталась. Однако комментариев в ридере скоро не станет совсем, даже приходящих из базза, поэтому хочется на всякий случай забрать из него всё своё. К счастью гугл старается быть хорошим и даёт возможность экспорта данных (Настройки → Импорт/экспорт). К несчастью, у активных пользователей вроде меня нередко скачиваются только данные за короткий период.

к счастью, гугл давно старается быть хорошим, поэтому он дал нам многостраничный atom feed. Вот страничка с моими рекомендациями и бесконечной промоткой назад при помощи параметра ?c= в адресе, а вот поток моих рекомендаций, в котором нет прямой ссылки на «предыдущую страницу», зато есть тег gr:continuation. Если его содержимое подставить в адрес потока как параметр ?c=, то результатом будут предыдущие 20 рекомендаций.

а вот простой скрипт на питоне, в который достаточно подставить id из адреса своего потока рекомендаций, и он скачает всю историю этого потока, что я с радостью и сделал для себя:

id = '10202714043885511706'

import os, urllib, xml.dom.minidom as dom

def get(continuation):
    url2 = url + continuation
    print url2
    text = ''.join(urllib.urlopen(url2))

    atom = dom.parseString(text)
    continuation = atom.getElementsByTagName('gr:continuation')
    continuation = continuation[0].firstChild.nodeValue
    date = atom.getElementsByTagName('updated')
    date = date[0].firstChild.nodeValue

    folder = date[:7]
    if not os.path.exists(folder): os.mkdir(folder)

    f = open(os.path.join(folder, date), 'w')
    f.write(text)
    f.close()

    return continuation


url = ('http://www.google.com/reader/public/atom/user%%2F' +\
    '%s%%2Fstate%%2Fcom.google%%2Fbroadcast?c=') % id
continuation = ''

while True: continuation = get(continuation)

в качестве дополнительного бонуса у меня теперь заработает нормальный поиск по своим же рекомендациям : )

, ,