add-link.pl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #! /usr/bin/perl
  2. # Copyright (C) 2011–2018 Alex Schroeder <alex@gnu.org>
  3. # This program is free software: you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation, either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License along with
  13. # this program. If not, see <http://www.gnu.org/licenses/>.
  14. package OddMuse;
  15. use LWP::UserAgent;
  16. use HTML::TreeBuilder;
  17. use JSON::PP;
  18. use utf8;
  19. # load Oddmuse core
  20. $RunCGI = 0;
  21. $DataDir = '/home/alex/campaignwiki';
  22. do "/home/alex/farm/wiki.pl";
  23. # globals depending on the name of the script
  24. my ($self, $name, $wiki);
  25. if ($0 eq '/home/alex/campaignwiki.org/add-link.pl') {
  26. $self = "https://campaignwiki.org/add-link";
  27. $name = "OSR Links to Wisdom";
  28. $wiki = 'LinksToWisdom';
  29. } elsif ($0 eq '/home/alex/campaignwiki.org/add-adventure.pl') {
  30. $self = "https://campaignwiki.org/add-adventure";
  31. $name = "OSR Links to Adventures";
  32. $wiki = 'Adventures';
  33. } elsif ($0 eq '/home/alex/campaignwiki.org/add-sf-link.pl') {
  34. $name = "OSRSF House Rules Wiki: Uplinked Intelligence";
  35. $wiki = 'UplinkedIntelligence';
  36. } else {
  37. ReportError('Cannot determine wiki!', '500 INTERNAL SERVER ERROR');
  38. }
  39. # derived variables
  40. my $site = "https://campaignwiki.org/wiki/$wiki";
  41. # my $site = "http://localhost/wiki.pl";
  42. my $home = "$site/$HomePage";
  43. # http://www.emacswiki.org/pics/star.png
  44. my $stardata = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEUAAHkAAACzdRTapx3twwD/9qb////1YCa0AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfXAQYCJAu+WhwbAAAAKnRFWHRDb21tZW50AGJ5IFJhZG9taXIgJ1RoZSBTaGVlcCcgRG9waWVybGFza2kVfTXbAAAAYElEQVQI12NgQAKMMIaYAFTAzRDKCHOEMETCnEFyjIJhYS6OggwMoqGhaS7GRgIMjC6uYc5GikA5YRcXIyWwotBgJUWw7lAXsAyDaIihMlhK1FFA0AjEEAESQgJQu4EYAPAPC2XcokgQAAAAAElFTkSuQmCC';
  45. main();
  46. sub canonical {
  47. my $url = shift;
  48. # handle blogspot domain munging
  49. $url =~ s/blogspot(\.[a-z]+)+/blogspot.com/;
  50. return $url;
  51. }
  52. sub toc {
  53. # start with the homepage
  54. my @values;
  55. my %labels;
  56. for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
  57. push @values, $id;
  58. for my $item (GetPageContent(FreeToNormal($id)) =~ /(\*+ [^][\n]*)$/mg) {
  59. my $value = $item;
  60. my $label = $item;
  61. $value =~ s/\* *//g;
  62. push @values, $value;
  63. $label =~ s/\* */ /g; # EM SPACE
  64. $labels{$value} = $label;
  65. }
  66. }
  67. return \@values, \%labels;
  68. }
  69. sub top {
  70. # start with the homepage
  71. my %blog;
  72. my $n;
  73. for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
  74. for my $item (GetPageContent(FreeToNormal($id)) =~ /^\*+\s+\[(https?:\/\/[^\/\n\t ]+)/mg) {
  75. $n++;
  76. $blog{canonical($item)}++;
  77. }
  78. }
  79. print $q->p("Total links counted: $n.");
  80. my @list = sort { $blog{$b} <=> $blog{$a} } keys %blog;
  81. # my $max = scalar @list;
  82. # $max = 20 if $max > 20;
  83. # @list = @list[0 .. $max -1];
  84. @list = map {
  85. my $domain = substr($_, index($_, '://') + 3);
  86. my $term = quotemeta($domain);
  87. # handle blogspot domain munging
  88. $term =~ s/blogspot\\\.com/blogspot(\\.[a-z]+)+/;
  89. $term = QuoteHtml($term);
  90. $q->a({-href => $_}, $domain)
  91. . " (" . $q->a({-href => "$self/match/$term"}, $blog{$_}) . ")";
  92. } @list;
  93. return \@list;
  94. }
  95. sub match {
  96. my $term = shift;
  97. # start with the homepage
  98. my @list;
  99. my $title;
  100. for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
  101. for my $line (split /\n/, GetPageContent(FreeToNormal($id))) {
  102. if ($line =~ /^\*+\s+([^][\n]*)$/) {
  103. $title = $1;
  104. } elsif ($line =~ /$term/o) {
  105. if ($line =~ /^\*+\s+\[(https?:\S+)\s+([^]]+)\]/) {
  106. push (@list, $q->a({-href => $1}, $2) . " (" . $title . ")");
  107. }
  108. }
  109. }
  110. }
  111. return \@list;
  112. }
  113. sub html_toc {
  114. my ($values, $labels) = toc();
  115. return $q->radio_group(-name =>'toc',
  116. -values => $values,
  117. -labels => $labels,
  118. -linebreak=>'true');
  119. }
  120. sub default {
  121. print $q->p("Add a link to the " . $q->a({-href=>$home}, $name) . ".");
  122. print $q->start_multipart_form(-method=>'get', -class=>'submit');
  123. print $q->p($q->label({-for=>'url'}, T('URL:')) . ' '
  124. . $q->textfield(-name=>'url', -id=>'url', -size=>80));
  125. print $q->p({-style=>'font-size: 10pt'},
  126. "(Drag this bookmarklet to your bookmarks bar for easy access:",
  127. $q->a({-href=>q{javascript:location='}
  128. . $q->url()
  129. . qq{?url='+encodeURIComponent(window.location.href)}},
  130. "Submit $name") . ".)");
  131. print html_toc();
  132. print $q->submit('go', 'Add!');
  133. print $q->end_form();
  134. }
  135. sub links {
  136. # start with the homepage
  137. my @links; # [["url", "title", "page id"], ...]
  138. for my $id (GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g) {
  139. for my $item (GetPageContent(FreeToNormal($id)) =~ /^\*+\s+\[(https?:\/\/.*?)\]/mg) {
  140. my ($url, $title) = split(/\s+/, $item, 2);
  141. push(@links, [$url, $title, $id]);
  142. }
  143. }
  144. return @links;
  145. }
  146. sub is_duplicate {
  147. my $url = shift;
  148. for my $link (links()) {
  149. if ($link->[0] eq $url) {
  150. print $q->p($q->strong("Oops, we seem to have a problem!"));
  151. print $q->p(GetPageLink(NormalToFree($link->[2])),
  152. " already links to the URL you submitted:",
  153. GetUrl($link->[0], $link->[1]));
  154. return 1;
  155. }
  156. }
  157. return 0;
  158. }
  159. sub confirm {
  160. my ($url, $name, $toc) = @_;
  161. print $q->p("Please confirm that you want to add "
  162. . GetUrl($url, $name)
  163. . " to the section “$toc”.");
  164. print $q->start_form(-method=>'get');
  165. print $q->p($q->label({-for=>'name', -style=>'display: inline-block; width: 15em'},
  166. T('Use a different link name:')) . ' '
  167. . $q->textfield(-style=>'display: inline-block; width:50ex',
  168. -name=>'name', -id=>'name', -size=>50, -default=>$name)
  169. . $q->br()
  170. . $q->label({-for=>'summary', -style=>'display: inline-block; width:15em'},
  171. T('An optional short summary:')) . ' '
  172. . $q->textfield(-style=>'display: inline-block; width:50ex',
  173. -name=>'summary', -id=>'summary', -size=>50)
  174. . $q->br()
  175. . $q->label({-for=>'username', -style=>'display: inline-block; width:15em'},
  176. T('Your name for the log file:')) . ' '
  177. . $q->textfield(-style=>'display: inline-block; width:50ex',
  178. -name=>'username', -id=>'username', -size=>50));
  179. my $star = $q->img({-src=>$stardata, -class=>'smiley', -alt=>'☆'});
  180. print '<p>Optionally: Do you want to rate it?<br />';
  181. my $i = 0;
  182. foreach my $label ($q->span({-style=>'display: inline-block; width:3em'}, $star)
  183. . 'I might use this for my campaign',
  184. $q->span({-style=>'display: inline-block; width:3em'}, $star x 2)
  185. . 'I have used this in a campaign and it worked as intended',
  186. $q->span({-style=>'display: inline-block; width:3em'}, $star x 3)
  187. . 'I have used this in a campaign and it was ' . $q->em('great')) {
  188. $i++;
  189. print qq{<label><input type="radio" name="stars" value="$i" $checked/>$label</label><br />};
  190. }
  191. print '</p>';
  192. print $q->hidden('url', $url);
  193. print $q->hidden('toc', $toc);
  194. print $q->hidden('confirm', 1);
  195. print $q->submit('go', 'Continue');
  196. print $q->end_form();
  197. }
  198. # returns unquoted html
  199. sub get_name {
  200. my $url = shift;
  201. my $tree = HTML::TreeBuilder->new_from_content(GetRaw($url));
  202. my $h = $tree->look_down('_tag', 'title');
  203. $h = $tree->look_down('_tag', 'h1') unless $h;
  204. $h = $h->as_text if $h;
  205. return $h;
  206. }
  207. sub post_addition {
  208. my ($url, $name, $toc, $summary) = @_;
  209. my $id = FreeToNormal($name);
  210. my $display = $name;
  211. utf8::decode($display); # we're dealing with user input
  212. utf8::decode($summary); # we're dealing with user input
  213. print $q->p("Adding ", GetUrl($url, $display), " to “$toc”.");
  214. # start with the homepage
  215. my @pages = GetPageContent($HomePage) =~ /\* \[\[(.*?)\]\]/g;
  216. for my $id (@pages) {
  217. return post($id, undef, $name, $summary, $url, GetParam('stars', '')) if $id eq $toc;
  218. my $data = GetPageContent(FreeToNormal($id));
  219. while ($data =~ /(\*+ ([^][\n]*))$/mg) {
  220. return post($id, $1, $name, $summary, $url, GetParam('stars', '')) if $2 eq $toc;
  221. }
  222. }
  223. print $q->p("Whoops. I was unable to find “$toc” in the wiki. Sorry!");
  224. }
  225. sub post {
  226. my ($id, $toc, $name, $summary, $url, $stars) = @_;
  227. my $data = GetPageContent(FreeToNormal($id));
  228. my $re = quotemeta($url);
  229. if ($data =~ /$re\s+(.*?)\]/) {
  230. my $display = $1;
  231. print $q->p($q->strong("Oops, we seem to have a problem!"));
  232. print $q->p(GetPageLink(NormalToFree($id)),
  233. " already links to the URL you submitted:",
  234. GetUrl($url, $display));
  235. return;
  236. }
  237. $stars = ' ' . (':star:' x $stars) if $stars;
  238. $summary = ': ' . $summary if $summary;
  239. if ($toc) {
  240. $toc =~ /^(\*+)/;
  241. my $depth = "*$1"; # one more!
  242. my $regexp = quotemeta($toc);
  243. $data =~ s/$regexp/$toc\n$depth \[$url $name\]$summary$stars/;
  244. } else {
  245. $data = "* [$url $name]$summary$stars\n" . $data;
  246. }
  247. my $ua = LWP::UserAgent->new;
  248. my %params = (text => $data,
  249. title => $id,
  250. summary => $name,
  251. username => GetParam('username'),
  252. pwd => GetParam('pwd'));
  253. # spam fighting modules
  254. $params{$QuestionaskerSecretKey} = 1 if $QuestionaskerSecretKey;
  255. $params{$HoneyPotOk} = time if $HoneyPotOk;
  256. my $response = $ua->post($site, \%params);
  257. if ($response->is_error) {
  258. print $q->p("The submission failed!");
  259. print $response->content;
  260. } else {
  261. print $q->p("See for yourself: ", GetPageLink($id));
  262. }
  263. }
  264. sub print_end_of_page {
  265. print $q->p('Questions? Send mail to Alex Schroeder <'
  266. . $q->a({-href=>'mailto:kensanata@gmail.com'},
  267. 'kensanata@gmail.com') . '>');
  268. print $q->end_div();
  269. PrintFooter();
  270. }
  271. sub main {
  272. $ConfigFile = "$DataDir/config"; # read the global config file
  273. $ModuleDir = "$DataDir/modules"; # global modules
  274. $DataDir = "$DataDir/$wiki"; # but link to the local pages
  275. Init(); # read config file
  276. $ScriptName = $site; # undo setting in the config file
  277. $FullUrl = $site; #
  278. InitPageVariables(); # call again: $ScriptName was wrong
  279. $HomePage = 'HomePage'; # $HomePage must not be translated
  280. binmode(STDOUT,':utf8');
  281. $q->charset('utf8');
  282. if ($q->path_info eq '/source') {
  283. seek DATA, 0, 0;
  284. print "Content-type: text/plain; charset=UTF-8\r\n\r\n", <DATA>;
  285. } elsif ($q->path_info eq '/structure') {
  286. my ($values, $labels) = toc();
  287. my @indented = map {
  288. ($labels->{$_} || $_) =~ /^( *)/;
  289. [$_, length($1)]
  290. } @$values;
  291. print "Content-type: application/json; charset=UTF-8\r\n\r\n";
  292. binmode(STDOUT,':raw'); # because of encode_json
  293. print JSON::PP::encode_json(\@indented);
  294. } elsif ($q->path_info eq '/toc') {
  295. my ($values, $labels) = toc();
  296. print "Content-type: application/json; charset=UTF-8\r\n\r\n";
  297. binmode(STDOUT,':raw'); # because of encode_json
  298. print JSON::PP::encode_json($values);
  299. } elsif ($q->path_info eq '/top') {
  300. print GetHeader('', 'Top Blogs');
  301. print $q->start_div({-class=>'content top'});
  302. print $q->ol($q->li(top()));
  303. print_end_of_page();
  304. } elsif ($q->path_info =~ '^/match/(.*)') {
  305. my $term = $1;
  306. print GetHeader('', "Entries Matching '$term'");
  307. print $q->start_div({-class=>'content match'});
  308. print $q->ol($q->li(match($term)));
  309. print_end_of_page();
  310. } else {
  311. push(@UserGotoBarPages, 'Help');
  312. $UserGotoBar = $q->a({-href=>$q->url . '/source'}, 'Source');
  313. print GetHeader('', 'Submit a new link');
  314. print $q->start_div({-class=>'content index'});
  315. my $url = canonical(GetParam('url'));
  316. my $name = UnquoteHtml(GetParam('name', get_name($url)));
  317. my $toc = GetParam('toc');
  318. my $confirm = GetParam('confirm');
  319. my $summary = GetParam('summary');
  320. if (not $url) {
  321. default();
  322. } elsif (not $toc) {
  323. default() if not is_duplicate($url);
  324. } elsif (not $confirm) {
  325. confirm($url, $name, $toc);
  326. } else {
  327. post_addition($url, $name, $toc, $summary);
  328. }
  329. print_end_of_page();
  330. }
  331. }
  332. __DATA__