bytebuster: (ITCrowd-Jen)
[personal profile] bytebuster
Ура, %SUBJ%, котани! Налітай!

Скрипт працює так:

  1. Додає от такий лінк на сторінку допису:
  2. По кліку на посилання відкриває http://www.dreamwidth.org/entry/new у новому вікні (target="_blank") та заповнює два поля: Subject та Body.
    Оновлено: з версії 0.8 вміє заповнювати також To-Journal, Icon, та Tags.
    Причому можна налаштовувати «шаблони» для перепосту: наприклад, «перепост до спільноти такої-то з юзерпикою такою-то і наперед прописаними тегами».

Known Bugs:

  • Не вставляється YouTube відео. Спробую побороти, але там багато возні. — Починив у v.0.3
  • Не розгортаються Cut Tags. Перед перепостом треба все руками розгортати
  • З тієї ж причини, Cut Tags у перепощеному дописі не завжди вставляються. Їх треба прописувати руками, якщо перепост робився зі сторінки конкретного допису. А якщо перепост зі сторінки журналу, то всі cut вставляються правильно.
  • Глючить, якщо пост містить <lj user="..."> — ім'я цього юзера помилково вставляється у шапку перепосту (замість імені того юзера, чий пост ви перепощуєте). — Починив у v.0.2

Зауваження:

  • Перше і найголовніше: Ніколи не встановлюйте скрипти від невідомих джерел!
    Особливо якщо ви не розумієте код.
  • Tags та To [Journal/Community] не прописує. Прописує з версії 0.8.
    Можливо, потім придумаю шаблони для перепостів, але поки що цього нема. Зроблено у версії 0.8.
  • Перевіряв на сторінках індивідуальних журналів (http://bytebuster.dreamwidth.org/), комун (http://bitter-onion.dreamwidth.org/) та індивідуальних дописів (http://bitter-onion.dreamwidth.org/1139072.html).
  • Перевіряв лише на Firefox. У кого Chrome, перевірте хто-небудь на Tampermonkey, га? Я не упевнений, що там працюють функції, специфічні для GM.
  • Багрепорти приймаються, але я не глибоко дружу з JavaScript, тому не гарантія, що я зможу вилікувати усі можливі проблеми.

Встановлення

  • Ставимо собі Greasemonkey;
  • Перезапускаємо Firefox;
  • Повертаємося до цього допису, виділяємо все тіло скрипта і копіюємо до Clipboard;
  • Тиць в кнопку Greasemonkey на Toolbar, тиць New User Script…;
  • У новому віконці внизу кнопка Use Script from Clipboard;
  • Налаштувати свої шаблони у секції:
    // **** SETTINGS ****************************************************
  • Зберегти;
  • Відкрити будь-яку сторінку на DreamWidth.
Сам скрипт залитий на BitBucket (завжди нова версія), а також (увага, стара версія!) під катом:

// ==UserScript==
// @name        DreamWidth Reposter
// @namespace   bytebuster.dreamwidth.org
// @description Adds LJ-like Repost functionality for DreamWidth
// @include     http://*.dreamwidth.org/*
// @exclude     http://*.dreamwidth.org/profile*
// @exclude     http://*.dreamwidth.org/calendar*
// @exclude     http://*.dreamwidth.org/tag/
// @version     0.3
// @grant       GM_log
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// ==/UserScript==

/* **********************************************************************************

    This script has two main functions:
    * addRepostLinks - triggers on all DreamWidth pages; adds "Repost" links to entries;
    * populatePostForm - triggers on /entry/new page; fills up the "Post Entry" fields

    Цей скрипт містить дві основні функції:
    * addRepostLinks - запускається на усіх сторінках DreamWidth pages; додає кнопку «Перепостити»;
    * populatePostForm - запускається на сторінці /entry/new page; заповнює поля вводу

********************************************************************************** */

/*
 * 0.3 fixed applied domain and URL;
       fixed Youtube video embed
       minor fix if mistakenly decided that post in community,
         while it actually is in user's own journal
 * 0.2 added group/blog references
       fix bug on other users references
 * 0.1 Initial reease
*/

(function(){
  // **** SETTINGS ****************************************************
  // Main settings
  // Головні налаштування
  const REPOST_TEXT = "Перепостити"; // Так виглядатиме текст кнопки «Репост»
  const REPOST_TEMPLATE = 'Перепост допису <lj user="{poster}">: <a href="{link}">{subj}</a>\n{body}';
  const REPOST_TEMPLATE_COMMUNITY = 'Перепост допису <lj user="{poster}"> @ <lj user="{community}">: <a href="{link}">{subj}</a>\n{body}';
  const DEBUG = false;

  // **** CONSTANTS ***************************************************
  // You don't need it unless DreamWidth changes its classes (impossible, yeah)
  // Вам тут не треба нічого міняти, допоки Дрім не поміняє імена класів (майже нереально)

  const CLS_ENTRY_TITLE   = "entry-title";
  const CLS_ENTRY_WRAPPER = "entry-wrapper";
  const CLS_ENTRY_POSTER  = "entry-poster";
  const CLS_LJUSER        = "ljuser";
  const ATTR_LJUSER       = "lj:user";
  const CLS_ENTRY_CONTENT = "entry-content";
  const CLS_ENTRY_LINKS   = "entry-interaction-links";
  const ID_INPUT_SUBJECT  = "id-subject-0";
  const ID_INPUT_BODY     = "id-event-1";
  const CLS_EMBED         = "lj_embedcontent-wrapper";

  const POST_URL = "http://www.dreamwidth.org/update";
  const POST_URL2 = "/entry/new";
  const REPOST_PARAM="repost";
  const CONTENT_DEFAULT = "";
  const CONTENT_TAG = "DW_REPOST_CONTENT"; // how we save the reposted entry in GM_setValue

  const REGEX_YOUTUBE_LINK     =  /^.*(?:youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/gmi; // /www.youtube.com\/watch?v=(.+?)/gmi;
  const EMBED_YOUTUBE_TEMPLATE = '<iframe width="640" height="360" src="https://www.youtube.com/embed/{videoID}" frameborder="0" allowfullscreen></iframe>';

// applies a parser on strings and log error if failed
function parse(parser, where, description) {
  var ret;
  match = parser.exec(where);
  if( match === null ) {
    GM_log("Failed to parse " + description + " from text: " + where);
    ret = "(null)";
  } else {
    ret = match[1];
  }
  return ret;
}

function DebugMessage( what ) {
  if(DEBUG) { GM_log(what); }
}

// checks for URL parameter
function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i=0;i<vars.length;i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return pair[1];
    }
  }
  DebugMessage('Query Variable ' + variable + ' not found');
}

// Adds Repost links to all entries on the page
// Додає посилання «Перепост» внизу усіх дописів на сторінці
function addRepostLinks() {
  DebugMessage("Adding Repost links begin...");
  var entries = document.getElementsByClassName(CLS_ENTRY_WRAPPER);

  for (var entry_index=0; entry_index<entries.length; entry_index++) {
    addRepostLink(entries[entry_index]);
  }
  DebugMessage("Adding Repost links end");
}

// Gets the saved content for repost
function getRepostData() {
  DebugMessage("getRepostData begin");

  return GM_getValue(CONTENT_TAG, CONTENT_DEFAULT);
}
// Sets the content for repost
function setRepostData(repostData) {
  DebugMessage("setRepostData begin " + repostData);
  // delete the state if it is the default
  if( repostData == CONTENT_DEFAULT ) {
    GM_deleteValue(CONTENT_TAG);
  } else {
    // set the state otherwise
    GM_setValue(CONTENT_TAG, repostData);
  }
  DebugMessage("setRepostData end");
}

// Saves JSON data from the reposted entry
// Зберігає допис і його шапку
function rememberPost(elEntry) {
  var elPoster, elLjuser, elTitle, elContent; // elements
  var entryPoster, entryCommunity, entryLink, entrySubject, entryBody, subdomain; // strings
  var repostData; // JSON object
  DebugMessage("rememberPost begin");

  subdomain = window.location.host.split(".")[0];
  // get Poster userId
  elPoster = elEntry.getElementsByClassName(CLS_ENTRY_POSTER)[0];

  if(elPoster && elPoster.hasChildNodes()) {
    // we seem to be in a community, parse out poster name from attribute
    elLjuser = elPoster.getElementsByClassName(CLS_LJUSER)[0];
    entryPoster = elLjuser.getAttribute(ATTR_LJUSER);
    entryCommunity = subdomain;
  } else {
    // we seem to be in a journal, parse out poster name from URL (FIXME)
    entryPoster = subdomain;
    entryCommunity = "";
  }
  // Quick fix: fix if we mistakenly set community
  if(entryPoster == entryCommunity) {
    entryCommunity = "";
  }

  // get Subj and Link
  elTitle = elEntry.getElementsByClassName(CLS_ENTRY_TITLE)[0];
  entryLink    = elTitle.getElementsByTagName("a")[0].href;
  entrySubject = elTitle.textContent;

  // get Body
  elContent = elEntry.getElementsByClassName(CLS_ENTRY_CONTENT)[0]
    .cloneNode(true);

  // Apply fixes
  elContent = FixYoutube(elContent);
  elContent = FixUsers(elContent);

  entryBody = elContent.innerHTML;

  repostData =
    JSON.stringify(
      {
        "link"      : entryLink,
        "poster"    : entryPoster,
        "community" : entryCommunity,
        "subj"      : entrySubject,
        "body"      : entryBody
      }
    );

  DebugMessage("Saving JSON: " + repostData);
  setRepostData(repostData);
  DebugMessage("rememberPost end");
}

// Fixes User references
// Takes a cloned node to fix
// Returns it as well
function FixUsers(element) {
  var embeds; // list of embed objects

  DebugMessage("FixUsers begin");

  embeds = element.getElementsByClassName(CLS_LJUSER);

  for (var embed_index=0; embed_index<embeds.length; embed_index++) {
    var elLjuser, elLjUser2; // elements
    var userName; // strings

    elLjuser = embeds[embed_index];
    if(elLjuser.nodeName != "SPAN") { DebugMessage("Element frame does not look like a user reference, exiting"); continue; }

    userName = elLjuser.getAttribute(ATTR_LJUSER);

    // creating an HTML fragment to replace the broken link
    elLjUser2 = document.createElement("lj");
    elLjUser2.setAttribute("user", userName);

    // replacing the element
    element.replaceChild(elLjUser2, elLjuser)
    embed_index--;
  }
  DebugMessage("FixUsers end");
  return element;
}

// Fixes YouTube links
// Takes a cloned node to fix
// Returns it as well
function FixYoutube(element) {
  var embeds; // list of embed objects

  DebugMessage("FixYoutube begin");

  embeds = element.getElementsByClassName(CLS_EMBED);

  for (var embed_index=0; embed_index<embeds.length; embed_index++) {
    var elFrame, elLinkDiv, elLink, elRange, elFragment, elFirstNode; // elements
    var linkAddress, linkBodyNew; // strings

    elFrame = embeds[embed_index];
    if(elFrame.nodeName != "DIV") { DebugMessage("Element frame does not look like a YouTube, exiting"); continue; }

    elLinkDiv = elFrame.nextSibling;
    if(elLinkDiv === null) { DebugMessage("Failed to find YouTube link DIV, exiting"); continue; }

    elLink = elLinkDiv.getElementsByTagName("a")[0];
    if(elLink === null) { DebugMessage("YouTube link seems to be broken, exiting"); continue; }

    linkAddress = parse(REGEX_YOUTUBE_LINK, elLink.href, "YouTube Link");
    linkBodyNew = EMBED_YOUTUBE_TEMPLATE
      .replace("{videoID}", linkAddress);

    // creating an HTML fragment to replace the broken link
    elRange = document.createRange();
    elRange.selectNode(document.body); // required in Safari
    elFragment = elRange.createContextualFragment(linkBodyNew);
    elFirstNode = elFragment.firstChild;

    // replacing the element
    element.removeChild(elFrame);
    element.replaceChild(elFirstNode, elLinkDiv)
    embed_index--;
  }
  DebugMessage("FixYoutube end");
  return element;
}

// Adds Repost links to one entry
// Додає посилання «Перепост» внизу допису
function addRepostLink(entry) {
  var elFooter, elRepostLink, elRepostLinkLI; // elements
  var entryTitle, entryLink, userName, entryId;

  // check if entry is a DOM element
  if( !entry.tagName ) { return; }

  elFooter = entry.getElementsByClassName(CLS_ENTRY_LINKS)[0];
  // must be < ul >

  elRepostLink = document.createElement("a");
  elRepostLink.href = POST_URL + "?" + REPOST_PARAM +"=1";
  elRepostLink.target = "_blank";
  elRepostLink.addEventListener('click', function(event) { rememberPost(entry); /*event.stopPropagation(); event.preventDefault();*/ }, true);
  elRepostLink.text = REPOST_TEXT;
  elRepostLinkLI = document.createElement("li");
  elRepostLinkLI.appendChild(elRepostLink);
  elFooter.appendChild(elRepostLinkLI);
}

// Fills up the "Post Entry" form
// Заповнює форму «Створити допис»
function populatePostForm() {
  DebugMessage("Populate Post begin");

  var repostedUrl = getQueryVariable(REPOST_PARAM);
  if(! repostedUrl) {
    DebugMessage("Does not look like a repost, exiting");
    return;
  }

  // continue

  var repostDataString, repostData; // JSON data
  var postBody; // strings
  var elSubject, elBody; // elements

  repostDataString = getRepostData();
  DebugMessage("Repost data=" + repostDataString);

  repostData = JSON.parse(repostDataString);
  DebugMessage("Repost data parsed=" + JSON.stringify(repostData));

  DebugMessage("Preparing postBody...");
  postBody =
    ((repostData.community == "") ? REPOST_TEMPLATE : REPOST_TEMPLATE_COMMUNITY)
    .replace('{poster}', repostData.poster)
    .replace('{community}', repostData.community)
    .replace('{link}', repostData.link)
    .replace('{subj}', repostData.subj)
    .replace('{body}', repostData.body)
  ;

  DebugMessage("postBody=" + postBody);

  elBody = document.getElementById(ID_INPUT_BODY);
  DebugMessage("elBody was: " + elBody.value);
  elBody.value = postBody;
  DebugMessage("elBody became: " + elBody.value);

  elSubject = document.getElementById(ID_INPUT_SUBJECT);
  DebugMessage("elSubject was: " + elSubject.value);
  elSubject.value = repostData.subj;
  DebugMessage("elSubject became: " + elSubject.value);
  setRepostData(CONTENT_DEFAULT);
  DebugMessage("Populate Post end");
}

  var thisUrl = window.location.href;
  if(thisUrl.indexOf(POST_URL2) != -1) {
    populatePostForm();
  } else {
    addRepostLinks();
  }
})();

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org

Сторінку створено Вівторок, 14 Квітень 2026 20:00

Грудень 2025

П В С Ч П С Н
1234567
891011121314
15161718192021
22232425262728
2930 31    
Створено з Dreamwidth Studios

За стиль дякувати