add-link.pl 12 KB

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