plugin.pm 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package BotPlugin;
  2. use common::sense;
  3. my %plugins = ();
  4. my %control_commands = ();
  5. my %control_states = ();
  6. my %irc_commands = ();
  7. sub init {
  8. $irc_commands{priv} = sub {
  9. my ($source, $targets, $args, $account) = @_;
  10. BotIrc::check_ctx(priv => 'priv') or return;
  11. my @args = split(/\s+/, $args, 3);
  12. if ($args[0] eq 'add') {
  13. for (split(/\s+/, $args[2])) {
  14. BotDb::add_priv(lc($args[1]), lc($_));
  15. }
  16. } elsif ($args[0] =~ /^(?:remove|rm|del|delete)$/) {
  17. for (split(/\s+/, $args[2])) {
  18. BotDb::del_priv(lc($args[1]), lc($_));
  19. }
  20. } else {
  21. BotIrc::send_noise("Invalid sub-command.");
  22. return 1;
  23. }
  24. BotIrc::send_noise("Okay.");
  25. return 1;
  26. };
  27. $irc_commands{plugin} = sub {
  28. my ($source, $targets, $args, $account) = @_;
  29. BotIrc::check_ctx(priv => 'plugin') or return;
  30. my @args = split(/\s+/, $args, 3);
  31. my $cb = sub { BotIrc::send_noise(shift) };
  32. if ($args[0] eq 'load') {
  33. load($args[1], $cb, $cb);
  34. } elsif ($args[0] eq 'unload') {
  35. unload($args[1], $cb, $cb);
  36. } elsif ($args[0] eq 'reload') {
  37. unload($args[1], $cb, $cb);
  38. load($args[1], $cb, $cb);
  39. } else {
  40. BotIrc::send_noise("Invalid sub-command.");
  41. return 1;
  42. }
  43. };
  44. $irc_commands{rehash} = sub {
  45. my ($source, $targets, $args, $account) = @_;
  46. BotIrc::check_ctx(priv => 'rehash') or return;
  47. # TODO: apply changes live as possible
  48. # $old_config = $BotIrc::config;
  49. BotIrc::read_config();
  50. BotIrc::send_noise("Okay.");
  51. };
  52. $irc_commands{user} = sub {
  53. my ($source, $targets, $args, $account) = @_;
  54. BotIrc::check_ctx(priv => 'user') or return;
  55. my @args = split(/\s+/, $args);
  56. if ($args[0] eq 'add') {
  57. if (@args != 2) {
  58. BotIrc::send_noise("Wrong number of args.");
  59. return 1;
  60. }
  61. BotDb::add_user(lc($args[1]));
  62. } elsif ($args[0] eq 'del') {
  63. if (@args != 2) {
  64. BotIrc::send_noise("Wrong number of args.");
  65. return 1;
  66. }
  67. BotDb::del_user(lc($args[1]));
  68. } elsif ($args[0] eq 'addmask') {
  69. if (@args != 3) {
  70. BotIrc::send_noise("Wrong number of args.");
  71. return 1;
  72. }
  73. BotDb::add_mask(lc($args[1]), $args[2]);
  74. } elsif ($args[0] eq 'delmask') {
  75. if (@args != 3) {
  76. BotIrc::send_noise("Wrong number of args.");
  77. return 1;
  78. }
  79. BotDb::del_mask(lc($args[1]), $args[2]);
  80. } elsif ($args[0] eq 'clearmasks') {
  81. if (@args != 2) {
  82. BotIrc::send_noise("Wrong number of args.");
  83. return 1;
  84. }
  85. BotDb::clear_masks(lc($args[1]));
  86. } else {
  87. BotIrc::send_noise("Invalid sub-command.");
  88. return 1;
  89. }
  90. BotIrc::send_noise("Okay.");
  91. return 1;
  92. };
  93. }
  94. sub load($;$$) {
  95. my $name = shift;
  96. my $error = shift // \&BotIrc::error;
  97. my $info = shift // \&BotIrc::info;
  98. my $p;
  99. if ($name eq 'core') {
  100. $error->("Can't load plugin 'core': reserved name");
  101. return undef;
  102. }
  103. if (exists $plugins{$name}) {
  104. $error->("Plugin '$name' already loaded.");
  105. return $plugins{$name};
  106. }
  107. unless ($p = do "plugins/$name.pm") {
  108. $error->("Couldn't parse plugin '$name': $@") if $@;
  109. $error->("Couldn't read plugin '$name': $!") if $!;
  110. return undef;
  111. }
  112. my @dbinfo = $BotDb::db->selectrow_array("SELECT * FROM plugins WHERE name=?", {}, $name);
  113. if (!@dbinfo) {
  114. $info->("Installing plugin '$name' during first load...");
  115. $BotDb::db->do("INSERT INTO plugins VALUES(?, 0)", {}, $name);
  116. $p->{on_install}($error, $info) if exists $p->{on_install};
  117. }
  118. if (exists $p->{dependencies}) {
  119. for (@{$p->{dependencies}}) {
  120. next if (exists $plugins{$_});
  121. $info->("Loading dependency '$_'...");
  122. if (!&load($_)) {
  123. $error->("Aborting load of '$name' due to the above error in dependency '$_'");
  124. return undef;
  125. }
  126. }
  127. }
  128. if (exists $p->{schemata}) {
  129. BotDb::update_schema($name, $p->{schemata}, $error, $info) or return undef;
  130. }
  131. _import_excl_handlers($p, $name, 'control_commands', \%control_commands, $error) or return 0;
  132. _import_excl_handlers($p, $name, 'control_states', \%control_states, $error) or return 0;
  133. _import_excl_handlers($p, $name, 'irc_commands', \%irc_commands, $error) or return 0;
  134. for (keys %$p) {
  135. next if !/^irc_on_(.+)$/;
  136. BotIrc::add_handler("irc_$1", $name, $p->{$_});
  137. }
  138. $p->{on_load}($error, $info) if exists $p->{on_load};
  139. $plugins{$name} = $p;
  140. $info->("Plugin '$name' loaded.");
  141. return $p;
  142. }
  143. sub unload($) {
  144. my $name = shift;
  145. my $error = shift // \&BotInfo::error;
  146. my $info = shift // \&BotInfo::info;
  147. return if !exists $plugins{$name};
  148. $plugins{$name}{before_unload}($error, $info) if exists $plugins{$name}{before_unload};
  149. _unimport_excl_handlers($name, 'control_commands', \%control_commands);
  150. _unimport_excl_handlers($name, 'control_states', \%control_states);
  151. _unimport_excl_handlers($name, 'irc_commands', \%irc_commands);
  152. BotIrc::remove_handlers($name);
  153. delete $plugins{$name};
  154. $info->("Plugin '$name' unloaded.");
  155. }
  156. sub call($$;@) {
  157. my ($name, $func, @params) = @_;
  158. croak("Tried to call plugin function in '$name' but isn't loaded") if !exists $plugins{$name};
  159. croak("Tried to call plugin function '$func' in '$name' but isn't defined") if !exists $plugins{$name}{functions}{$func};
  160. return $plugins{$name}{functions}{$func}(@params);
  161. }
  162. # Interface used by other parts of the core ############################## {{{
  163. sub maybe_irc_command($$$$$) {
  164. my ($source, $targets, $cmd, $args, $auth) = @_;
  165. return 0 if (!exists $irc_commands{$cmd});
  166. $irc_commands{$cmd}($source, $targets, $args, $auth);
  167. return 1;
  168. }
  169. sub maybe_ctl_command($$$$) {
  170. my ($client, $data, $cmd, @args) = @_;
  171. return 0 if (!exists $control_commands{$cmd});
  172. $control_commands{$cmd}($client, $data, @args);
  173. return 1;
  174. }
  175. sub add_core_ctl_command($$) {
  176. my ($cmd, $code) = @_;
  177. $control_commands{$cmd} = $code;
  178. }
  179. # }}}
  180. # Internal helpers ####################################################### {{{
  181. sub _import_excl_handlers($$$$) {
  182. my ($p, $name, $type, $target, $error) = @_;
  183. $error //= \&BotIrc::error;
  184. my $eh = $p->{$type};
  185. my @eh = grep { exists $target->{$_} } keys(%$eh);
  186. if (@eh) {
  187. $error->("While loading plugin $name: plugin tried to redefine the following $type: ".join(', ', @eh));
  188. return 0;
  189. }
  190. for (keys %$eh) { $target->{$_} = $eh->{$_}}
  191. return 1;
  192. }
  193. sub _unimport_excl_handlers($$) {
  194. my ($name, $type, $target) = @_;
  195. my $p = $plugins{$name};
  196. my $eh = $p->{$type};
  197. for (keys %$eh) { delete $target->{$_}; }
  198. }
  199. # }}}
  200. 1;