duckduckgo.pm 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. use POE;
  2. use HTTP::Request::Common;
  3. use JSON;
  4. use Mojo::DOM;
  5. use URI::Escape;
  6. my $dolink = sub { BotPlugin::call('templink', 'make', shift); };
  7. {
  8. dependencies => [ 'templink' ],
  9. irc_commands => {
  10. info => sub {
  11. my ($source, $targets, $args, $account) = @_;
  12. BotIrc::check_ctx() or return;
  13. my $nick = BotIrc::nickonly($source);
  14. my $ctx = BotIrc::ctx_frozen;
  15. BotHttp::get("https://api.duckduckgo.com/?q=".uri_escape($args)."&format=json&no_redirect=1&no_html=1&skip_disambig=1", sub {
  16. my $data = eval { decode_json(shift); };
  17. if ($@) {
  18. BotIrc::send_noise($ctx, ".info error: parsing JSON: $@");
  19. return;
  20. }
  21. # Most interested in: related topics
  22. if ($data->{Type} =~ /^[CD]$/o) {
  23. my @topics = @{$data->{RelatedTopics}};
  24. my $suffix = (@topics > 5) ? " | ..." : "";
  25. splice @topics, 5;
  26. my $topics = join(" | ", map { "$_->{Text} <". $dolink->($_->{FirstURL}) .">" } @topics);
  27. BotIrc::send_wisdom($ctx, "$topics$suffix");
  28. return;
  29. }
  30. # Bang command
  31. if ($data->{Redirect}) {
  32. BotIrc::send_wisdom($ctx, $dolink->($data->{Redirect}));
  33. return;
  34. }
  35. # Answer from calculator etc.
  36. if ($data->{Answer} && $data->{AnswerType}) {
  37. BotIrc::send_wisdom($ctx, "[$data->{AnswerType}] $data->{Answer}");
  38. return;
  39. }
  40. # Abstract
  41. if ($data->{AbstractText}) {
  42. BotIrc::send_wisdom($ctx, "$data->{Heading}: $data->{AbstractText} <$data->{AbstractURL}> [from $data->{AbstractSource}]");
  43. return;
  44. }
  45. # Definition
  46. if ($data->{Definition}) {
  47. BotIrc::send_wisdom($ctx, "$data->{Definition} <$data->{DefinitionURL}> [from $data->{DefinitionSource}]");
  48. return;
  49. }
  50. BotIrc::send_wisdom($ctx, ".info: nothing found.");
  51. }, sub {
  52. BotIrc::send_noise($ctx, ".info error: query '$args' failed: ".shift);
  53. return;
  54. });
  55. },
  56. search => sub {
  57. my ($source, $targets, $args, $auth) = @_;
  58. BotIrc::check_ctx() or return;
  59. my $nick = BotIrc::nickonly($source);
  60. my $req = POST('https://duckduckgo.com/html', [q => $args]);
  61. my $ctx = BotIrc::ctx_frozen;
  62. BotHttp::request($req, sub {
  63. my $dom;
  64. eval {
  65. $dom = Mojo::DOM->new(shift);
  66. };
  67. if ($@) {
  68. BotIrc::send_noise($ctx, ".search error: parsing HTML: $@");
  69. return;
  70. }
  71. my $nodes = $dom->find('.web-result');
  72. if (!$nodes || !@$nodes) {
  73. BotIrc::send_wisdom($ctx, ".search: nothing found.");
  74. return;
  75. }
  76. my $suffix = (@$nodes > 3) ? " | ..." : "";
  77. splice @$nodes, 3;
  78. $nodes = [ map {
  79. my $url = $_->at('.result__a')->attr('href');
  80. my $title = $_->at('.result__a')->all_text;
  81. $url = $dolink->($url) if $url;
  82. $url ? "$title <$url>" : undef;
  83. } @$nodes ];
  84. if (!$nodes->[0]) {
  85. BotIrc::send_wisdom($ctx, ".search: nothing found.");
  86. return;
  87. }
  88. $nodes = join(" | ", @$nodes);
  89. BotIrc::send_wisdom($ctx, "$nodes$suffix");
  90. }, sub {
  91. BotIrc::send_noise($ctx, ".search error: query '$args' failed: ".shift);
  92. return;
  93. });
  94. },
  95. },
  96. };