Advertisement
Guest User

sopel patch

a guest
Jun 2nd, 2016
232
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 189.33 KB | None | 0 0
  1. From 7022da1f72428e8b90a861a6f4bcbeb16cb5e45c Mon Sep 17 00:00:00 2001
  2. From: S00ng <???>
  3. Date: Sun, 1 May 2016 01:48:56 +0200
  4. Subject: [PATCH 1/4] iniz machine learning ready
  5.  
  6. ---
  7. sopel/modules/__init__.py | 2 -
  8. sopel/modules/admin.py | 229 ----------------------
  9. sopel/modules/adminchannel.py | 276 --------------------------
  10. sopel/modules/announce.py | 23 ---
  11. sopel/modules/bugzilla.py | 96 ----------
  12. sopel/modules/calc.py | 70 -------
  13. sopel/modules/clock.py | 277 ---------------------------
  14. sopel/modules/countdown.py | 39 ----
  15. sopel/modules/currency.py | 105 ----------
  16. sopel/modules/dice.py | 259 -------------------------
  17. sopel/modules/etymology.py | 97 ----------
  18. sopel/modules/find.py | 139 --------------
  19. sopel/modules/find_updates.py | 58 ------
  20. sopel/modules/help.py | 72 -------
  21. sopel/modules/ip.py | 139 --------------
  22. sopel/modules/ipython.py | 78 --------
  23. sopel/modules/isup.py | 36 ----
  24. sopel/modules/lmgtfy.py | 19 --
  25. sopel/modules/meetbot.py | 436 ------------------------------------------
  26. sopel/modules/movie.py | 48 -----
  27. sopel/modules/ping.py | 29 ---
  28. sopel/modules/rand.py | 49 -----
  29. sopel/modules/reddit.py | 191 ------------------
  30. sopel/modules/reload.py | 139 --------------
  31. sopel/modules/remind.py | 228 ----------------------
  32. sopel/modules/safety.py | 197 -------------------
  33. sopel/modules/search.py | 128 -------------
  34. sopel/modules/seen.py | 59 ------
  35. sopel/modules/spellcheck.py | 54 ------
  36. sopel/modules/tell.py | 183 ------------------
  37. sopel/modules/tld.py | 69 -------
  38. sopel/modules/translate.py | 203 --------------------
  39. sopel/modules/unicode_info.py | 49 -----
  40. sopel/modules/units.py | 186 ------------------
  41. sopel/modules/uptime.py | 27 ---
  42. sopel/modules/url.py | 237 -----------------------
  43. sopel/modules/version.py | 81 --------
  44. sopel/modules/weather.py | 183 ------------------
  45. sopel/modules/wikipedia.py | 133 -------------
  46. sopel/modules/wiktionary.py | 101 ----------
  47. sopel/modules/xkcd.py | 105 ----------
  48. sopel/trigger.py | 5 +-
  49. 42 files changed, 3 insertions(+), 5131 deletions(-)
  50. delete mode 100644 sopel/modules/__init__.py
  51. delete mode 100644 sopel/modules/admin.py
  52. delete mode 100644 sopel/modules/adminchannel.py
  53. delete mode 100644 sopel/modules/announce.py
  54. delete mode 100644 sopel/modules/bugzilla.py
  55. delete mode 100644 sopel/modules/calc.py
  56. delete mode 100644 sopel/modules/clock.py
  57. delete mode 100644 sopel/modules/countdown.py
  58. delete mode 100644 sopel/modules/currency.py
  59. delete mode 100644 sopel/modules/dice.py
  60. delete mode 100644 sopel/modules/etymology.py
  61. delete mode 100644 sopel/modules/find.py
  62. delete mode 100644 sopel/modules/find_updates.py
  63. delete mode 100644 sopel/modules/help.py
  64. delete mode 100644 sopel/modules/ip.py
  65. delete mode 100644 sopel/modules/ipython.py
  66. delete mode 100644 sopel/modules/isup.py
  67. delete mode 100644 sopel/modules/lmgtfy.py
  68. delete mode 100644 sopel/modules/meetbot.py
  69. delete mode 100644 sopel/modules/movie.py
  70. delete mode 100644 sopel/modules/ping.py
  71. delete mode 100644 sopel/modules/rand.py
  72. delete mode 100644 sopel/modules/reddit.py
  73. delete mode 100644 sopel/modules/reload.py
  74. delete mode 100644 sopel/modules/remind.py
  75. delete mode 100644 sopel/modules/safety.py
  76. delete mode 100644 sopel/modules/search.py
  77. delete mode 100644 sopel/modules/seen.py
  78. delete mode 100644 sopel/modules/spellcheck.py
  79. delete mode 100644 sopel/modules/tell.py
  80. delete mode 100644 sopel/modules/tld.py
  81. delete mode 100644 sopel/modules/translate.py
  82. delete mode 100644 sopel/modules/unicode_info.py
  83. delete mode 100644 sopel/modules/units.py
  84. delete mode 100644 sopel/modules/uptime.py
  85. delete mode 100644 sopel/modules/url.py
  86. delete mode 100644 sopel/modules/version.py
  87. delete mode 100644 sopel/modules/weather.py
  88. delete mode 100644 sopel/modules/wikipedia.py
  89. delete mode 100644 sopel/modules/wiktionary.py
  90. delete mode 100644 sopel/modules/xkcd.py
  91.  
  92. diff --git a/sopel/modules/__init__.py b/sopel/modules/__init__.py
  93. deleted file mode 100644
  94. index b6f85b6..0000000
  95. --- a/sopel/modules/__init__.py
  96. +++ /dev/null
  97. @@ -1,2 +0,0 @@
  98. -# coding=utf-8
  99. -from __future__ import unicode_literals, absolute_import, print_function, division
  100. diff --git a/sopel/modules/admin.py b/sopel/modules/admin.py
  101. deleted file mode 100644
  102. index 14e84aa..0000000
  103. --- a/sopel/modules/admin.py
  104. +++ /dev/null
  105. @@ -1,229 +0,0 @@
  106. -# coding=utf-8
  107. -"""
  108. -admin.py - Sopel Admin Module
  109. -Copyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich
  110. -(yanovich.net)
  111. -Copyright © 2012, Elad Alfassa, <[email protected]>
  112. -Copyright 2013, Ari Koivula <[email protected]>
  113. -
  114. -Licensed under the Eiffel Forum License 2.
  115. -
  116. -http://sopel.chat
  117. -"""
  118. -from __future__ import unicode_literals, absolute_import, print_function, division
  119. -
  120. -from sopel.config.types import (
  121. - StaticSection, ValidatedAttribute, FilenameAttribute
  122. -)
  123. -import sopel.module
  124. -
  125. -
  126. -class AdminSection(StaticSection):
  127. - hold_ground = ValidatedAttribute('hold_ground', bool, default=False)
  128. - """Auto re-join on kick"""
  129. - auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,
  130. - default=True)
  131. -
  132. -
  133. -def configure(config):
  134. - config.define_section('admin', AdminSection)
  135. - config.admin.configure_setting('hold_ground',
  136. - "Automatically re-join after being kicked?")
  137. - config.admin.configure_setting('auto_accept_invite',
  138. - 'Automatically join channels when invited?')
  139. -
  140. -
  141. -def setup(bot):
  142. - bot.config.define_section('admin', AdminSection)
  143. -
  144. -
  145. [email protected]('.join #example or .join #example key')
  146. -def join(bot, trigger):
  147. - """Join the specified channel. This is an admin-only command."""
  148. - channel, key = trigger.group(3), trigger.group(4)
  149. - if not channel:
  150. - return
  151. - elif not key:
  152. - bot.join(channel)
  153. - else:
  154. - bot.join(channel, key)
  155. -
  156. -
  157. [email protected]('.part #example')
  158. -def part(bot, trigger):
  159. - """Part the specified channel. This is an admin-only command."""
  160. - channel, _sep, part_msg = trigger.group(2).partition(' ')
  161. - if part_msg:
  162. - bot.part(channel, part_msg)
  163. - else:
  164. - bot.part(channel)
  165. -
  166. -
  167. -def quit(bot, trigger):
  168. - """Quit from the server. This is an owner-only command."""
  169. - quit_message = trigger.group(2)
  170. - if not quit_message:
  171. - quit_message = 'Quitting on command from %s' % trigger.nick
  172. -
  173. - bot.quit(quit_message)
  174. -
  175. -
  176. [email protected]('.msg #YourPants Does anyone else smell neurotoxin?')
  177. -def msg(bot, trigger):
  178. - """
  179. - Send a message to a given channel or nick. Can only be done in privmsg by an
  180. - admin.
  181. - """
  182. - if trigger.group(2) is None:
  183. - return
  184. -
  185. - channel, _sep, message = trigger.group(2).partition(' ')
  186. - message = message.strip()
  187. - if not channel or not message:
  188. - return
  189. -
  190. - bot.msg(channel, message)
  191. -
  192. -
  193. -def me(bot, trigger):
  194. - """
  195. - Send an ACTION (/me) to a given channel or nick. Can only be done in privmsg
  196. - by an admin.
  197. - """
  198. - if trigger.group(2) is None:
  199. - return
  200. -
  201. - channel, _sep, action = trigger.group(2).partition(' ')
  202. - action = action.strip()
  203. - if not channel or not action:
  204. - return
  205. -
  206. - msg = '\x01ACTION %s\x01' % action
  207. - bot.msg(channel, msg)
  208. -
  209. -
  210. -def invite_join(bot, trigger):
  211. - """
  212. - Join a channel sopel is invited to, if the inviter is an admin.
  213. - """
  214. - if trigger.admin or bot.config.admin.auto_accept_invite:
  215. - bot.join(trigger.args[1])
  216. - return
  217. -
  218. -
  219. -def hold_ground(bot, trigger):
  220. - """
  221. - This function monitors all kicks across all channels sopel is in. If it
  222. - detects that it is the one kicked it'll automatically join that channel.
  223. -
  224. - WARNING: This may not be needed and could cause problems if sopel becomes
  225. - annoying. Please use this with caution.
  226. - """
  227. - if bot.config.admin.hold_ground:
  228. - channel = trigger.sender
  229. - if trigger.args[1] == bot.nick:
  230. - bot.join(channel)
  231. -
  232. -
  233. -def mode(bot, trigger):
  234. - """Set a user mode on Sopel. Can only be done in privmsg by an admin."""
  235. - mode = trigger.group(3)
  236. - bot.write(('MODE ', bot.nick + ' ' + mode))
  237. -
  238. -
  239. [email protected]_privmsg("This command only works as a private message.")
  240. [email protected]_admin("This command requires admin privileges.")
  241. [email protected]('.set core.owner Me')
  242. -def set_config(bot, trigger):
  243. - """See and modify values of sopels config object.
  244. -
  245. - Trigger args:
  246. - arg1 - section and option, in the form "section.option"
  247. - arg2 - value
  248. -
  249. - If there is no section, section will default to "core".
  250. - If value is None, the option will be deleted.
  251. - """
  252. - # Get section and option from first argument.
  253. - arg1 = trigger.group(3).split('.')
  254. - if len(arg1) == 1:
  255. - section_name, option = "core", arg1[0]
  256. - elif len(arg1) == 2:
  257. - section_name, option = arg1
  258. - else:
  259. - bot.reply("Usage: .set section.option value")
  260. - return
  261. - section = getattr(bot.config, section_name)
  262. - static_sec = isinstance(section, StaticSection)
  263. -
  264. - if static_sec and not hasattr(section, option):
  265. - bot.say('[{}] section has no option {}.'.format(section_name, option))
  266. - return
  267. -
  268. - # Display current value if no value is given.
  269. - value = trigger.group(4)
  270. - if not value:
  271. - if not static_sec and bot.config.parser.has_option(section, option):
  272. - bot.reply("Option %s.%s does not exist." % (section_name, option))
  273. - return
  274. - # Except if the option looks like a password. Censor those to stop them
  275. - # from being put on log files.
  276. - if option.endswith("password") or option.endswith("pass"):
  277. - value = "(password censored)"
  278. - else:
  279. - value = getattr(section, option)
  280. - bot.reply("%s.%s = %s" % (section_name, option, value))
  281. - return
  282. -
  283. - # Otherwise, set the value to one given as argument 2.
  284. - if static_sec:
  285. - descriptor = getattr(section.__class__, option)
  286. - try:
  287. - if isinstance(descriptor, FilenameAttribute):
  288. - value = descriptor.parse(bot.config, descriptor, value)
  289. - else:
  290. - value = descriptor.parse(value)
  291. - except ValueError as exc:
  292. - bot.say("Can't set attribute: " + str(exc))
  293. - return
  294. - setattr(section, option, value)
  295. -
  296. -
  297. -def save_config(bot, trigger):
  298. - """Save state of sopels config object to the configuration file."""
  299. - bot.config.save()
  300. diff --git a/sopel/modules/adminchannel.py b/sopel/modules/adminchannel.py
  301. deleted file mode 100644
  302. index e3fe10f..0000000
  303. --- a/sopel/modules/adminchannel.py
  304. +++ /dev/null
  305. @@ -1,276 +0,0 @@
  306. -# coding=utf-8
  307. -# Copyright 2010-2011, Michael Yanovich, Alek Rollyson, and Elsie Powell
  308. -# Copyright © 2012, Elad Alfassa <[email protected]>
  309. -# Licensed under the Eiffel Forum License 2.
  310. -from __future__ import unicode_literals, absolute_import, print_function, division
  311. -
  312. -import re
  313. -from sopel import formatting
  314. -from sopel.module import commands, priority, OP, HALFOP, require_privilege, require_chanmsg
  315. -from sopel.tools import Identifier
  316. -
  317. -
  318. -def default_mask(trigger):
  319. - welcome = formatting.color('Welcome to:', formatting.colors.PURPLE)
  320. - chan = formatting.color(trigger.sender, formatting.colors.TEAL)
  321. - topic_ = formatting.bold('Topic:')
  322. - topic_ = formatting.color('| ' + topic_, formatting.colors.PURPLE)
  323. - arg = formatting.color('{}', formatting.colors.GREEN)
  324. - return '{} {} {} {}'.format(welcome, chan, topic_, arg)
  325. -
  326. -
  327. -@require_chanmsg
  328. -@require_privilege(OP, 'You are not a channel operator.')
  329. -@commands('kick')
  330. -@priority('high')
  331. -def kick(bot, trigger):
  332. - """
  333. - Kick a user from the channel.
  334. - """
  335. - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
  336. - return bot.reply("I'm not a channel operator!")
  337. - text = trigger.group().split()
  338. - argc = len(text)
  339. - if argc < 2:
  340. - return
  341. - opt = Identifier(text[1])
  342. - nick = opt
  343. - channel = trigger.sender
  344. - reasonidx = 2
  345. - if not opt.is_nick():
  346. - if argc < 3:
  347. - return
  348. - nick = text[2]
  349. - channel = opt
  350. - reasonidx = 3
  351. - reason = ' '.join(text[reasonidx:])
  352. - if nick != bot.config.core.nick:
  353. - bot.write(['KICK', channel, nick], reason)
  354. -
  355. -
  356. -def configureHostMask(mask):
  357. - if mask == '*!*@*':
  358. - return mask
  359. - if re.match('^[^.@!/]+$', mask) is not None:
  360. - return '%s!*@*' % mask
  361. - if re.match('^[^@!]+$', mask) is not None:
  362. - return '*!*@%s' % mask
  363. -
  364. - m = re.match('^([^!@]+)@$', mask)
  365. - if m is not None:
  366. - return '*!%s@*' % m.group(1)
  367. -
  368. - m = re.match('^([^!@]+)@([^@!]+)$', mask)
  369. - if m is not None:
  370. - return '*!%s@%s' % (m.group(1), m.group(2))
  371. -
  372. - m = re.match('^([^!@]+)!(^[!@]+)@?$', mask)
  373. - if m is not None:
  374. - return '%s!%s@*' % (m.group(1), m.group(2))
  375. - return ''
  376. -
  377. -
  378. -@require_chanmsg
  379. -@require_privilege(OP, 'You are not a channel operator.')
  380. -@commands('ban')
  381. -@priority('high')
  382. -def ban(bot, trigger):
  383. - """
  384. - This give admins the ability to ban a user.
  385. - The bot must be a Channel Operator for this command to work.
  386. - """
  387. - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
  388. - return bot.reply("I'm not a channel operator!")
  389. - text = trigger.group().split()
  390. - argc = len(text)
  391. - if argc < 2:
  392. - return
  393. - opt = Identifier(text[1])
  394. - banmask = opt
  395. - channel = trigger.sender
  396. - if not opt.is_nick():
  397. - if argc < 3:
  398. - return
  399. - channel = opt
  400. - banmask = text[2]
  401. - banmask = configureHostMask(banmask)
  402. - if banmask == '':
  403. - return
  404. - bot.write(['MODE', channel, '+b', banmask])
  405. -
  406. -
  407. -@require_chanmsg
  408. -@require_privilege(OP, 'You are not a channel operator.')
  409. -@commands('unban')
  410. -def unban(bot, trigger):
  411. - """
  412. - This give admins the ability to unban a user.
  413. - The bot must be a Channel Operator for this command to work.
  414. - """
  415. - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
  416. - return bot.reply("I'm not a channel operator!")
  417. - text = trigger.group().split()
  418. - argc = len(text)
  419. - if argc < 2:
  420. - return
  421. - opt = Identifier(text[1])
  422. - banmask = opt
  423. - channel = trigger.sender
  424. - if not opt.is_nick():
  425. - if argc < 3:
  426. - return
  427. - channel = opt
  428. - banmask = text[2]
  429. - banmask = configureHostMask(banmask)
  430. - if banmask == '':
  431. - return
  432. - bot.write(['MODE', channel, '-b', banmask])
  433. -
  434. -
  435. -@require_chanmsg
  436. -@require_privilege(OP, 'You are not a channel operator.')
  437. -@commands('quiet')
  438. -def quiet(bot, trigger):
  439. - """
  440. - This gives admins the ability to quiet a user.
  441. - The bot must be a Channel Operator for this command to work.
  442. - """
  443. - if bot.privileges[trigger.sender][bot.nick] < OP:
  444. - return bot.reply("I'm not a channel operator!")
  445. - text = trigger.group().split()
  446. - argc = len(text)
  447. - if argc < 2:
  448. - return
  449. - opt = Identifier(text[1])
  450. - quietmask = opt
  451. - channel = trigger.sender
  452. - if not opt.is_nick():
  453. - if argc < 3:
  454. - return
  455. - quietmask = text[2]
  456. - channel = opt
  457. - quietmask = configureHostMask(quietmask)
  458. - if quietmask == '':
  459. - return
  460. - bot.write(['MODE', channel, '+q', quietmask])
  461. -
  462. -
  463. -@require_chanmsg
  464. -@require_privilege(OP, 'You are not a channel operator.')
  465. -@commands('unquiet')
  466. -def unquiet(bot, trigger):
  467. - """
  468. - This gives admins the ability to unquiet a user.
  469. - The bot must be a Channel Operator for this command to work.
  470. - """
  471. - if bot.privileges[trigger.sender][bot.nick] < OP:
  472. - return bot.reply("I'm not a channel operator!")
  473. - text = trigger.group().split()
  474. - argc = len(text)
  475. - if argc < 2:
  476. - return
  477. - opt = Identifier(text[1])
  478. - quietmask = opt
  479. - channel = trigger.sender
  480. - if not opt.is_nick():
  481. - if argc < 3:
  482. - return
  483. - quietmask = text[2]
  484. - channel = opt
  485. - quietmask = configureHostMask(quietmask)
  486. - if quietmask == '':
  487. - return
  488. - bot.write(['MODE', channel, '-q', quietmask])
  489. -
  490. -
  491. -@require_chanmsg
  492. -@require_privilege(OP, 'You are not a channel operator.')
  493. -@commands('kickban', 'kb')
  494. -@priority('high')
  495. -def kickban(bot, trigger):
  496. - """
  497. - This gives admins the ability to kickban a user.
  498. - The bot must be a Channel Operator for this command to work.
  499. - .kickban [#chan] user1 user!*@* get out of here
  500. - """
  501. - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
  502. - return bot.reply("I'm not a channel operator!")
  503. - text = trigger.group().split()
  504. - argc = len(text)
  505. - if argc < 4:
  506. - return
  507. - opt = Identifier(text[1])
  508. - nick = opt
  509. - mask = text[2]
  510. - channel = trigger.sender
  511. - reasonidx = 3
  512. - if not opt.is_nick():
  513. - if argc < 5:
  514. - return
  515. - channel = opt
  516. - nick = text[2]
  517. - mask = text[3]
  518. - reasonidx = 4
  519. - reason = ' '.join(text[reasonidx:])
  520. - mask = configureHostMask(mask)
  521. - if mask == '':
  522. - return
  523. - bot.write(['MODE', channel, '+b', mask])
  524. - bot.write(['KICK', channel, nick], reason)
  525. -
  526. -
  527. -@require_chanmsg
  528. -@require_privilege(OP, 'You are not a channel operator.')
  529. -@commands('topic')
  530. -def topic(bot, trigger):
  531. - """
  532. - This gives ops the ability to change the topic.
  533. - The bot must be a Channel Operator for this command to work.
  534. - """
  535. - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
  536. - return bot.reply("I'm not a channel operator!")
  537. - if not trigger.group(2):
  538. - return
  539. - channel = trigger.sender.lower()
  540. -
  541. - narg = 1
  542. - mask = None
  543. - mask = bot.db.get_channel_value(channel, 'topic_mask')
  544. - mask = mask or default_mask(trigger)
  545. - mask = mask.replace('%s', '{}')
  546. - narg = len(re.findall('{}', mask))
  547. -
  548. - top = trigger.group(2)
  549. - args = []
  550. - if top:
  551. - args = top.split('~', narg)
  552. -
  553. - if len(args) != narg:
  554. - message = "Not enough arguments. You gave {}, it requires {}.".format(
  555. - len(args), narg)
  556. - return bot.say(message)
  557. - topic = mask.format(*args)
  558. -
  559. - bot.write(('TOPIC', channel + ' :' + topic))
  560. -
  561. -
  562. -@require_chanmsg
  563. -@require_privilege(OP, 'You are not a channel operator.')
  564. -@commands('tmask')
  565. -def set_mask(bot, trigger):
  566. - """
  567. - Set the mask to use for .topic in the current channel. {} is used to allow
  568. - substituting in chunks of text.
  569. - """
  570. - bot.db.set_channel_value(trigger.sender, 'topic_mask', trigger.group(2))
  571. - bot.say("Gotcha, " + trigger.nick)
  572. -
  573. -
  574. -@require_chanmsg
  575. -@require_privilege(OP, 'You are not a channel operator.')
  576. -@commands('showmask')
  577. -def show_mask(bot, trigger):
  578. - """Show the topic mask for the current channel."""
  579. - mask = bot.db.get_channel_value(trigger.sender, 'topic_mask')
  580. - mask = mask or default_mask(trigger)
  581. - bot.say(mask)
  582. diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py
  583. deleted file mode 100644
  584. index 2b3df4b..0000000
  585. --- a/sopel/modules/announce.py
  586. +++ /dev/null
  587. @@ -1,23 +0,0 @@
  588. -# coding=utf-8
  589. -"""
  590. -announce.py - Send a message to all channels
  591. -Copyright © 2013, Elad Alfassa, <[email protected]>
  592. -Licensed under the Eiffel Forum License 2.
  593. -
  594. -"""
  595. -from __future__ import unicode_literals, absolute_import, print_function, division
  596. -
  597. -from sopel.module import commands, example
  598. -
  599. -
  600. -@commands('announce')
  601. -@example('.announce Some important message here')
  602. -def announce(bot, trigger):
  603. - """
  604. - Send an announcement to all channels the bot is in
  605. - """
  606. - if not trigger.admin:
  607. - bot.reply('Sorry, I can\'t let you do that')
  608. - return
  609. - for channel in bot.channels:
  610. - bot.msg(channel, '[ANNOUNCEMENT] %s' % trigger.group(2))
  611. diff --git a/sopel/modules/bugzilla.py b/sopel/modules/bugzilla.py
  612. deleted file mode 100644
  613. index 9167128..0000000
  614. --- a/sopel/modules/bugzilla.py
  615. +++ /dev/null
  616. @@ -1,96 +0,0 @@
  617. -# coding=utf-8
  618. -"""Bugzilla issue reporting module
  619. -
  620. -Copyright 2013-2015, Embolalia, embolalia.com
  621. -Licensed under the Eiffel Forum License 2.
  622. -"""
  623. -from __future__ import unicode_literals, absolute_import, print_function, division
  624. -
  625. -import re
  626. -
  627. -import xmltodict
  628. -
  629. -from sopel import web, tools
  630. -from sopel.config.types import StaticSection, ListAttribute
  631. -from sopel.logger import get_logger
  632. -from sopel.module import rule
  633. -
  634. -
  635. -regex = None
  636. -LOGGER = get_logger(__name__)
  637. -
  638. -
  639. -class BugzillaSection(StaticSection):
  640. - domains = ListAttribute('domains')
  641. - """The domains of the Bugzilla instances from which to get information."""
  642. -
  643. -
  644. -def configure(config):
  645. - config.define_section('bugzilla', BugzillaSection)
  646. - config.bugzilla.configure_setting(
  647. - 'domains',
  648. - 'Enter the domains of the Bugzillas you want extra information '
  649. - 'from (e.g. bugzilla.gnome.org)'
  650. - )
  651. -
  652. -
  653. -def setup(bot):
  654. - global regex
  655. - bot.config.define_section('bugzilla', BugzillaSection)
  656. -
  657. - if not bot.config.bugzilla.domains:
  658. - return
  659. - if not bot.memory.contains('url_callbacks'):
  660. - bot.memory['url_callbacks'] = tools.SopelMemory()
  661. -
  662. - domains = '|'.join(bot.config.bugzilla.domains)
  663. - regex = re.compile((r'https?://(%s)'
  664. - '(/show_bug.cgi\?\S*?)'
  665. - '(id=\d+)')
  666. - % domains)
  667. - bot.memory['url_callbacks'][regex] = show_bug
  668. -
  669. -
  670. -def shutdown(bot):
  671. - del bot.memory['url_callbacks'][regex]
  672. -
  673. -
  674. -@rule(r'.*https?://(\S+?)'
  675. - '(/show_bug.cgi\?\S*?)'
  676. - '(id=\d+).*')
  677. -def show_bug(bot, trigger, match=None):
  678. - """Show information about a Bugzilla bug."""
  679. - match = match or trigger
  680. - domain = match.group(1)
  681. - if domain not in bot.config.bugzilla.domains:
  682. - return
  683. - url = 'https://%s%sctype=xml&%s' % match.groups()
  684. - data = web.get(url, dont_decode=True)
  685. - bug = xmltodict.parse(data).get('bugzilla').get('bug')
  686. - error = bug.get('@error', None) # error="NotPermitted"
  687. -
  688. - if error:
  689. - LOGGER.warning('Bugzilla error: %s', error)
  690. - return
  691. -
  692. - message = ('[BUGZILLA] %s | Product: %s | Component: %s | Version: %s | ' +
  693. - 'Importance: %s | Status: %s | Assigned to: %s | ' +
  694. - 'Reported: %s | Modified: %s')
  695. -
  696. - resolution = bug.get('resolution')
  697. - if resolution is not None:
  698. - status = bug.get('bug_status') + ' ' + resolution
  699. - else:
  700. - status = bug.get('bug_status')
  701. -
  702. - assigned_to = bug.get('assigned_to')
  703. - if isinstance(assigned_to, dict):
  704. - assigned_to = assigned_to.get('@name')
  705. -
  706. - message = message % (
  707. - bug.get('short_desc'), bug.get('product'),
  708. - bug.get('component'), bug.get('version'),
  709. - (bug.get('priority') + ' ' + bug.get('bug_severity')),
  710. - status, assigned_to, bug.get('creation_ts'),
  711. - bug.get('delta_ts'))
  712. - bot.say(message)
  713. diff --git a/sopel/modules/calc.py b/sopel/modules/calc.py
  714. deleted file mode 100644
  715. index 6497b74..0000000
  716. --- a/sopel/modules/calc.py
  717. +++ /dev/null
  718. @@ -1,70 +0,0 @@
  719. -# coding=utf-8
  720. -"""
  721. -calc.py - Sopel Calculator Module
  722. -Copyright 2008, Sean B. Palmer, inamidst.com
  723. -Licensed under the Eiffel Forum License 2.
  724. -
  725. -http://sopel.chat
  726. -"""
  727. -from __future__ import unicode_literals, absolute_import, print_function, division
  728. -
  729. -import re
  730. -from sopel import web
  731. -from sopel.module import commands, example
  732. -from sopel.tools.calculation import eval_equation
  733. -from socket import timeout
  734. -import sys
  735. -if sys.version_info.major < 3:
  736. - import HTMLParser
  737. -else:
  738. - unichr = chr
  739. - import html.parser as HTMLParser
  740. -
  741. -
  742. -BASE_TUMBOLIA_URI = 'https://tumbolia-two.appspot.com/'
  743. -
  744. -
  745. -@commands('c', 'calc')
  746. -@example('.c 5 + 3', '8')
  747. -@example('.c 0.9*10', '9')
  748. -@example('.c 10*0.9', '9')
  749. -@example('.c 2*(1+2)*3', '18')
  750. -@example('.c 2**10', '1024')
  751. -@example('.c 5 // 2', '2')
  752. -@example('.c 5 / 2', '2.5')
  753. -def c(bot, trigger):
  754. - """Evaluate some calculation."""
  755. - if not trigger.group(2):
  756. - return bot.reply("Nothing to calculate.")
  757. - # Account for the silly non-Anglophones and their silly radix point.
  758. - eqn = trigger.group(2).replace(',', '.')
  759. - try:
  760. - result = eval_equation(eqn)
  761. - result = "{:.10g}".format(result)
  762. - except ZeroDivisionError:
  763. - result = "Division by zero is not supported in this universe."
  764. - except Exception as e:
  765. - result = "{error}: {msg}".format(error=type(e), msg=e)
  766. - bot.reply(result)
  767. -
  768. -
  769. -@commands('py')
  770. -@example('.py len([1,2,3])', '3')
  771. -def py(bot, trigger):
  772. - """Evaluate a Python expression."""
  773. - if not trigger.group(2):
  774. - return bot.say("Need an expression to evaluate")
  775. -
  776. - query = trigger.group(2)
  777. - uri = BASE_TUMBOLIA_URI + 'py/'
  778. - answer = web.get(uri + web.quote(query))
  779. - if answer:
  780. - #bot.say can potentially lead to 3rd party commands triggering.
  781. - bot.reply(answer)
  782. - else:
  783. - bot.reply('Sorry, no result.')
  784. -
  785. -
  786. -if __name__ == "__main__":
  787. - from sopel.test_tools import run_example_tests
  788. - run_example_tests(__file__)
  789. diff --git a/sopel/modules/clock.py b/sopel/modules/clock.py
  790. deleted file mode 100644
  791. index d7fa8bb..0000000
  792. --- a/sopel/modules/clock.py
  793. +++ /dev/null
  794. @@ -1,277 +0,0 @@
  795. -# coding=utf-8
  796. -# Copyright 2008-9, Sean B. Palmer, inamidst.com
  797. -# Copyright 2012, Elsie Powell, embolalia.com
  798. -# Licensed under the Eiffel Forum License 2.
  799. -from __future__ import unicode_literals, absolute_import, print_function, division
  800. -
  801. -try:
  802. - import pytz
  803. -except ImportError:
  804. - pytz = None
  805. -
  806. -from sopel.module import commands, example, OP
  807. -from sopel.tools.time import (
  808. - get_timezone, format_time, validate_format, validate_timezone
  809. -)
  810. -from sopel.config.types import StaticSection, ValidatedAttribute
  811. -
  812. -
  813. -class TimeSection(StaticSection):
  814. - tz = ValidatedAttribute(
  815. - 'tz',
  816. - parse=validate_timezone,
  817. - serialize=validate_timezone,
  818. - default='UTC'
  819. - )
  820. - """Default time zone (see http://sopel.chat/tz)"""
  821. - time_format = ValidatedAttribute(
  822. - 'time_format',
  823. - parse=validate_format,
  824. - default='%Y-%m-%d - %T%Z'
  825. - )
  826. - """Default time format (see http://strftime.net)"""
  827. -
  828. -
  829. -def configure(config):
  830. - config.define_section('clock', TimeSection)
  831. - config.clock.configure_setting(
  832. - 'tz', 'Preferred time zone (http://sopel.chat/tz)')
  833. - config.clock.configure_setting(
  834. - 'time_format', 'Preferred time format (http://strftime.net)')
  835. -
  836. -
  837. -def setup(bot):
  838. - bot.config.define_section('clock', TimeSection)
  839. -
  840. -
  841. -@commands('t', 'time')
  842. -@example('.t America/New_York')
  843. -def f_time(bot, trigger):
  844. - """Returns the current time."""
  845. - if trigger.group(2):
  846. - zone = get_timezone(bot.db, bot.config, trigger.group(2).strip(), None, None)
  847. - if not zone:
  848. - bot.say('Could not find timezone %s.' % trigger.group(2).strip())
  849. - return
  850. - else:
  851. - zone = get_timezone(bot.db, bot.config, None, trigger.nick,
  852. - trigger.sender)
  853. - time = format_time(bot.db, bot.config, zone, trigger.nick, trigger.sender)
  854. - bot.say(time)
  855. -
  856. -
  857. -@commands('settz', 'settimezone')
  858. -@example('.settz America/New_York')
  859. -def update_user(bot, trigger):
  860. - """
  861. - Set your preferred time zone. Most timezones will work, but it's best to
  862. - use one from http://sopel.chat/tz
  863. - """
  864. - if not pytz:
  865. - bot.reply("Sorry, I don't have timezone support installed.")
  866. - else:
  867. - tz = trigger.group(2)
  868. - if not tz:
  869. - bot.reply("What timezone do you want to set? Try one from "
  870. - "http://sopel.chat/tz")
  871. - return
  872. - if tz not in pytz.all_timezones:
  873. - bot.reply("I don't know that time zone. Try one from "
  874. - "http://sopel.chat/tz")
  875. - return
  876. -
  877. - bot.db.set_nick_value(trigger.nick, 'timezone', tz)
  878. - if len(tz) < 7:
  879. - bot.say("Okay, {}, but you should use one from http://sopel.chat/tz "
  880. - "if you use DST.".format(trigger.nick))
  881. - else:
  882. - bot.reply('I now have you in the %s time zone.' % tz)
  883. -
  884. -
  885. -@commands('gettz', 'gettimezone')
  886. -@example('.gettz [nick]')
  887. -def get_user_tz(bot, trigger):
  888. - """
  889. - Gets a user's preferred time zone, will show yours if no user specified
  890. - """
  891. - if not pytz:
  892. - bot.reply("Sorry, I don't have timezone support installed.")
  893. - else:
  894. - nick = trigger.group(2)
  895. - if not nick:
  896. - nick = trigger.nick
  897. -
  898. - nick = nick.strip()
  899. -
  900. - tz = bot.db.get_nick_value(nick, 'timezone')
  901. - if tz:
  902. - bot.say('%s\'s time zone is %s.' % (nick, tz))
  903. - else:
  904. - bot.say('%s has not set their time zone' % nick)
  905. -
  906. -
  907. -@commands('settimeformat', 'settf')
  908. -@example('.settf %Y-%m-%dT%T%z')
  909. -def update_user_format(bot, trigger):
  910. - """
  911. - Sets your preferred format for time. Uses the standard strftime format. You
  912. - can use http://strftime.net or your favorite search engine to learn more.
  913. - """
  914. - tformat = trigger.group(2)
  915. - if not tformat:
  916. - bot.reply("What format do you want me to use? Try using"
  917. - " http://strftime.net to make one.")
  918. - return
  919. -
  920. - tz = get_timezone(bot.db, bot.config, None, trigger.nick, trigger.sender)
  921. -
  922. - # Get old format as back-up
  923. - old_format = bot.db.get_nick_value(trigger.nick, 'time_format')
  924. -
  925. - # Save the new format in the database so we can test it.
  926. - bot.db.set_nick_value(trigger.nick, 'time_format', tformat)
  927. -
  928. - try:
  929. - timef = format_time(db=bot.db, zone=tz, nick=trigger.nick)
  930. - except:
  931. - bot.reply("That format doesn't work. Try using"
  932. - " http://strftime.net to make one.")
  933. - # New format doesn't work. Revert save in database.
  934. - bot.db.set_nick_value(trigger.nick, 'time_format', old_format)
  935. - return
  936. - bot.reply("Got it. Your time will now appear as %s. (If the "
  937. - "timezone is wrong, you might try the settz command)"
  938. - % timef)
  939. -
  940. -
  941. -@commands('gettimeformat', 'gettf')
  942. -@example('.gettf [nick]')
  943. -def get_user_format(bot, trigger):
  944. - """
  945. - Gets a user's preferred time format, will show yours if no user specified
  946. - """
  947. - nick = trigger.group(2)
  948. - if not nick:
  949. - nick = trigger.nick
  950. -
  951. - nick = nick.strip()
  952. -
  953. - # Get old format as back-up
  954. - format = bot.db.get_nick_value(nick, 'time_format')
  955. -
  956. - if format:
  957. - bot.say("%s's time format: %s." % (nick, format))
  958. - else:
  959. - bot.say("%s hasn't set a custom time format" % nick)
  960. -
  961. -
  962. -@commands('setchanneltz', 'setctz')
  963. -@example('.setctz America/New_York')
  964. -def update_channel(bot, trigger):
  965. - """
  966. - Set the preferred time zone for the channel.
  967. - """
  968. - if bot.privileges[trigger.sender][trigger.nick] < OP:
  969. - return
  970. - elif not pytz:
  971. - bot.reply("Sorry, I don't have timezone support installed.")
  972. - else:
  973. - tz = trigger.group(2)
  974. - if not tz:
  975. - bot.reply("What timezone do you want to set? Try one from "
  976. - "http://sopel.chat/tz")
  977. - return
  978. - if tz not in pytz.all_timezones:
  979. - bot.reply("I don't know that time zone. Try one from "
  980. - "http://sopel.chat/tz")
  981. - return
  982. -
  983. - bot.db.set_channel_value(trigger.sender, 'timezone', tz)
  984. - if len(tz) < 7:
  985. - bot.say("Okay, {}, but you should use one from http://sopel.chat/tz "
  986. - "if you use DST.".format(trigger.nick))
  987. - else:
  988. - bot.reply(
  989. - 'I now have {} in the {} time zone.'.format(trigger.sender, tz))
  990. -
  991. -
  992. -@commands('getchanneltz', 'getctz')
  993. -@example('.getctz [channel]')
  994. -def get_channel_tz(bot, trigger):
  995. - """
  996. - Gets the preferred channel timezone, or the current channel timezone if no
  997. - channel given.
  998. - """
  999. - if not pytz:
  1000. - bot.reply("Sorry, I don't have timezone support installed.")
  1001. - else:
  1002. - channel = trigger.group(2)
  1003. - if not channel:
  1004. - channel = trigger.sender
  1005. -
  1006. - channel = channel.strip()
  1007. -
  1008. - timezone = bot.db.get_channel_value(channel, 'timezone')
  1009. - if timezone:
  1010. - bot.say('%s\'s timezone: %s' % (channel, timezone))
  1011. - else:
  1012. - bot.say('%s has no preferred timezone' % channel)
  1013. -
  1014. -
  1015. -@commands('setchanneltimeformat', 'setctf')
  1016. -@example('.setctf %Y-%m-%dT%T%z')
  1017. -def update_channel_format(bot, trigger):
  1018. - """
  1019. - Sets your preferred format for time. Uses the standard strftime format. You
  1020. - can use http://strftime.net or your favorite search engine to learn more.
  1021. - """
  1022. - if bot.privileges[trigger.sender][trigger.nick] < OP:
  1023. - return
  1024. -
  1025. - tformat = trigger.group(2)
  1026. - if not tformat:
  1027. - bot.reply("What format do you want me to use? Try using"
  1028. - " http://strftime.net to make one.")
  1029. -
  1030. - tz = get_timezone(bot.db, bot.config, None, None, trigger.sender)
  1031. -
  1032. - # Get old format as back-up
  1033. - old_format = bot.db.get_channel_value(trigger.sender, 'time_format')
  1034. -
  1035. - # Save the new format in the database so we can test it.
  1036. - bot.db.set_channel_value(trigger.sender, 'time_format', tformat)
  1037. -
  1038. - try:
  1039. - timef = format_time(db=bot.db, zone=tz, channel=trigger.sender)
  1040. - except:
  1041. - bot.reply("That format doesn't work. Try using"
  1042. - " http://strftime.net to make one.")
  1043. - # New format doesn't work. Revert save in database.
  1044. - bot.db.set_channel_value(trigger.sender, 'time_format', old_format)
  1045. - return
  1046. - bot.db.set_channel_value(trigger.sender, 'time_format', tformat)
  1047. - bot.reply("Got it. Times in this channel will now appear as %s "
  1048. - "unless a user has their own format set. (If the timezone"
  1049. - " is wrong, you might try the settz and channeltz "
  1050. - "commands)" % timef)
  1051. -
  1052. -
  1053. -@commands('getchanneltimeformat', 'getctf')
  1054. -@example('.getctf [channel]')
  1055. -def get_channel_format(bot, trigger):
  1056. - """
  1057. - Gets the channel's preferred time format, will return current channel's if
  1058. - no channel name is given
  1059. - """
  1060. -
  1061. - channel = trigger.group(2)
  1062. - if not channel:
  1063. - channel = trigger.sender
  1064. -
  1065. - channel = channel.strip()
  1066. -
  1067. - tformat = bot.db.get_channel_value(channel, 'time_format')
  1068. - if tformat:
  1069. - bot.say('%s\'s time format: %s' % (channel, tformat))
  1070. - else:
  1071. - bot.say('%s has no preferred time format' % channel)
  1072. diff --git a/sopel/modules/countdown.py b/sopel/modules/countdown.py
  1073. deleted file mode 100644
  1074. index 23a6df8..0000000
  1075. --- a/sopel/modules/countdown.py
  1076. +++ /dev/null
  1077. @@ -1,39 +0,0 @@
  1078. -# coding=utf-8
  1079. -"""
  1080. -countdown.py - Sopel Countdown Module
  1081. -Copyright 2011, Michael Yanovich, yanovich.net
  1082. -Licensed under the Eiffel Forum License 2.
  1083. -
  1084. -http://sopel.chat
  1085. -"""
  1086. -from __future__ import unicode_literals, absolute_import, print_function, division
  1087. -from sopel.module import commands, NOLIMIT
  1088. -import datetime
  1089. -
  1090. -
  1091. -@commands('countdown')
  1092. -def generic_countdown(bot, trigger):
  1093. - """
  1094. - .countdown <year> <month> <day> - displays a countdown to a given date.
  1095. - """
  1096. - text = trigger.group(2)
  1097. - if not text:
  1098. - bot.say("Please use correct format: .countdown 2012 12 21")
  1099. - return NOLIMIT
  1100. - text = trigger.group(2).split()
  1101. - if text and (len(text) == 3 and text[0].isdigit() and text[1].isdigit()
  1102. - and text[2].isdigit()):
  1103. - try:
  1104. - diff = (datetime.datetime(int(text[0]), int(text[1]), int(text[2]))
  1105. - - datetime.datetime.today())
  1106. - except:
  1107. - bot.say("Please use correct format: .countdown 2012 12 21")
  1108. - return NOLIMIT
  1109. - bot.say(str(diff.days) + " days, " + str(diff.seconds // 3600)
  1110. - + " hours and "
  1111. - + str(diff.seconds % 3600 // 60)
  1112. - + " minutes until "
  1113. - + text[0] + " " + text[1] + " " + text[2])
  1114. - else:
  1115. - bot.say("Please use correct format: .countdown 2012 12 21")
  1116. - return NOLIMIT
  1117. diff --git a/sopel/modules/currency.py b/sopel/modules/currency.py
  1118. deleted file mode 100644
  1119. index 3bf0183..0000000
  1120. --- a/sopel/modules/currency.py
  1121. +++ /dev/null
  1122. @@ -1,105 +0,0 @@
  1123. -# coding=utf-8
  1124. -# Copyright 2013 Elsie Powell, embolalia.com
  1125. -# Licensed under the Eiffel Forum License 2
  1126. -from __future__ import unicode_literals, absolute_import, print_function, division
  1127. -
  1128. -import json
  1129. -import xmltodict
  1130. -import re
  1131. -
  1132. -from sopel import web
  1133. -from sopel.module import commands, example, NOLIMIT
  1134. -
  1135. -# The Canadian central bank has better exchange rate data than the Fed, the
  1136. -# Bank of England, or the European Central Bank. Who knew?
  1137. -base_url = 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_{}.xml'
  1138. -regex = re.compile(r'''
  1139. - (\d+(?:\.\d+)?) # Decimal number
  1140. - \s*([a-zA-Z]{3}) # 3-letter currency code
  1141. - \s+(?:in|as|of|to)\s+ # preposition
  1142. - ([a-zA-Z]{3}) # 3-letter currency code
  1143. - ''', re.VERBOSE)
  1144. -
  1145. -
  1146. -def get_rate(code):
  1147. - code = code.upper()
  1148. - if code == 'CAD':
  1149. - return 1, 'Canadian Dollar'
  1150. - elif code == 'BTC':
  1151. - rates = json.loads(web.get('https://api.bitcoinaverage.com/ticker/all'))
  1152. - return 1 / rates['CAD']['24h_avg'], 'Bitcoin—24hr average'
  1153. -
  1154. - data, headers = web.get(base_url.format(code), dont_decode=True, return_headers=True)
  1155. - if headers['_http_status'] == 404:
  1156. - return False, False
  1157. - namespaces = {
  1158. - 'http://www.cbwiki.net/wiki/index.php/Specification_1.1': 'cb',
  1159. - 'http://purl.org/rss/1.0/': None,
  1160. - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf'
  1161. - }
  1162. - xml = xmltodict.parse(data, process_namespaces=True, namespaces=namespaces).get('rdf:RDF')
  1163. - namestring = xml.get('channel').get('title').get('#text')
  1164. - name = namestring[len('Bank of Canada noon rate: '):]
  1165. - name = re.sub(r'\s*\(noon\)\s*', '', name)
  1166. - rate = xml.get('item').get('cb:statistics').get('cb:exchangeRate').get('cb:value').get('#text')
  1167. - return float(rate), name
  1168. -
  1169. -
  1170. -@commands('cur', 'currency', 'exchange')
  1171. -@example('.cur 20 EUR in USD')
  1172. -def exchange(bot, trigger):
  1173. - """Show the exchange rate between two currencies"""
  1174. - if not trigger.group(2):
  1175. - return bot.reply("No search term. An example: .cur 20 EUR in USD")
  1176. - match = regex.match(trigger.group(2))
  1177. - if not match:
  1178. - # It's apologetic, because it's using Canadian data.
  1179. - bot.reply("Sorry, I didn't understand the input.")
  1180. - return NOLIMIT
  1181. -
  1182. - amount, of, to = match.groups()
  1183. - try:
  1184. - amount = float(amount)
  1185. - except:
  1186. - bot.reply("Sorry, I didn't understand the input.")
  1187. - display(bot, amount, of, to)
  1188. -
  1189. -
  1190. -def display(bot, amount, of, to):
  1191. - if not amount:
  1192. - bot.reply("Zero is zero, no matter what country you're in.")
  1193. - try:
  1194. - of_rate, of_name = get_rate(of)
  1195. - if not of_name:
  1196. - bot.reply("Unknown currency: %s" % of)
  1197. - return
  1198. - to_rate, to_name = get_rate(to)
  1199. - if not to_name:
  1200. - bot.reply("Unknown currency: %s" % to)
  1201. - return
  1202. - except Exception:
  1203. - bot.reply("Something went wrong while I was getting the exchange rate.")
  1204. - return NOLIMIT
  1205. -
  1206. - result = amount / of_rate * to_rate
  1207. - bot.say("{} {} ({}) = {} {} ({})".format(amount, of.upper(), of_name,
  1208. - result, to.upper(), to_name))
  1209. -
  1210. -
  1211. -@commands('btc', 'bitcoin')
  1212. -@example('.btc 20 EUR')
  1213. -def bitcoin(bot, trigger):
  1214. - #if 2 args, 1st is number and 2nd is currency. If 1 arg, it's either the number or the currency.
  1215. - to = trigger.group(4)
  1216. - amount = trigger.group(3)
  1217. - if not to:
  1218. - to = trigger.group(3) or 'USD'
  1219. - amount = 1
  1220. -
  1221. - try:
  1222. - amount = float(amount)
  1223. - except:
  1224. - bot.reply("Sorry, I didn't understand the input.")
  1225. - return NOLIMIT
  1226. -
  1227. - display(bot, amount, 'BTC', to)
  1228. diff --git a/sopel/modules/dice.py b/sopel/modules/dice.py
  1229. deleted file mode 100644
  1230. index 2ac0d06..0000000
  1231. --- a/sopel/modules/dice.py
  1232. +++ /dev/null
  1233. @@ -1,259 +0,0 @@
  1234. -# coding=utf-8
  1235. -"""
  1236. -dice.py - Dice Module
  1237. -Copyright 2010-2013, Dimitri "Tyrope" Molenaars, TyRope.nl
  1238. -Copyright 2013, Ari Koivula, <[email protected]>
  1239. -Licensed under the Eiffel Forum License 2.
  1240. -
  1241. -http://sopel.chat/
  1242. -"""
  1243. -from __future__ import unicode_literals, absolute_import, print_function, division
  1244. -import random
  1245. -import re
  1246. -import operator
  1247. -
  1248. -import sopel.module
  1249. -from sopel.tools.calculation import eval_equation
  1250. -
  1251. -
  1252. -class DicePouch:
  1253. - def __init__(self, num_of_die, type_of_die, addition):
  1254. - """Initialize dice pouch and roll the dice.
  1255. -
  1256. - Args:
  1257. - num_of_die: number of dice in the pouch.
  1258. - type_of_die: how many faces the dice have.
  1259. - addition: how much is added to the result of the dice.
  1260. - """
  1261. - self.num = num_of_die
  1262. - self.type = type_of_die
  1263. - self.addition = addition
  1264. -
  1265. - self.dice = {}
  1266. - self.dropped = {}
  1267. -
  1268. - self.roll_dice()
  1269. -
  1270. - def roll_dice(self):
  1271. - """Roll all the dice in the pouch."""
  1272. - self.dice = {}
  1273. - self.dropped = {}
  1274. - for __ in range(self.num):
  1275. - number = random.randint(1, self.type)
  1276. - count = self.dice.setdefault(number, 0)
  1277. - self.dice[number] = count + 1
  1278. -
  1279. - def drop_lowest(self, n):
  1280. - """Drop n lowest dice from the result.
  1281. -
  1282. - Args:
  1283. - n: the number of dice to drop.
  1284. - """
  1285. -
  1286. - sorted_x = sorted(self.dice.items(), key=operator.itemgetter(0))
  1287. -
  1288. - for i, count in sorted_x:
  1289. - count = self.dice[i]
  1290. - if n == 0:
  1291. - break
  1292. - elif n < count:
  1293. - self.dice[i] = count - n
  1294. - self.dropped[i] = n
  1295. - break
  1296. - else:
  1297. - self.dice[i] = 0
  1298. - self.dropped[i] = count
  1299. - n = n - count
  1300. -
  1301. - for i, count in self.dropped.items():
  1302. - if self.dice[i] == 0:
  1303. - del self.dice[i]
  1304. -
  1305. - def get_simple_string(self):
  1306. - """Return the values of the dice like (2+2+2[+1+1])+1."""
  1307. - dice = self.dice.items()
  1308. - faces = ("+".join([str(face)] * times) for face, times in dice)
  1309. - dice_str = "+".join(faces)
  1310. -
  1311. - dropped_str = ""
  1312. - if self.dropped:
  1313. - dropped = self.dropped.items()
  1314. - dfaces = ("+".join([str(face)] * times) for face, times in dropped)
  1315. - dropped_str = "[+%s]" % ("+".join(dfaces),)
  1316. -
  1317. - plus_str = ""
  1318. - if self.addition:
  1319. - plus_str = "{:+d}".format(self.addition)
  1320. -
  1321. - return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
  1322. -
  1323. - def get_compressed_string(self):
  1324. - """Return the values of the dice like (3x2[+2x1])+1."""
  1325. - dice = self.dice.items()
  1326. - faces = ("%dx%d" % (times, face) for face, times in dice)
  1327. - dice_str = "+".join(faces)
  1328. -
  1329. - dropped_str = ""
  1330. - if self.dropped:
  1331. - dropped = self.dropped.items()
  1332. - dfaces = ("%dx%d" % (times, face) for face, times in dropped)
  1333. - dropped_str = "[+%s]" % ("+".join(dfaces),)
  1334. -
  1335. - plus_str = ""
  1336. - if self.addition:
  1337. - plus_str = "{:+d}".format(self.addition)
  1338. -
  1339. - return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
  1340. -
  1341. - def get_sum(self):
  1342. - """Get the sum of non-dropped dice and the addition."""
  1343. - result = self.addition
  1344. - for face, times in self.dice.items():
  1345. - result += face * times
  1346. - return result
  1347. -
  1348. - def get_number_of_faces(self):
  1349. - """Returns sum of different faces for dropped and not dropped dice
  1350. -
  1351. - This can be used to estimate, whether the result can be shown in
  1352. - compressed form in a reasonable amount of space.
  1353. - """
  1354. - return len(self.dice) + len(self.dropped)
  1355. -
  1356. -
  1357. -def _roll_dice(bot, dice_expression):
  1358. - result = re.search(
  1359. - r"""
  1360. - (?P<dice_num>-?\d*)
  1361. - d
  1362. - (?P<dice_type>-?\d+)
  1363. - (v(?P<drop_lowest>-?\d+))?
  1364. - $""",
  1365. - dice_expression,
  1366. - re.IGNORECASE | re.VERBOSE)
  1367. -
  1368. - dice_num = int(result.group('dice_num') or 1)
  1369. - dice_type = int(result.group('dice_type'))
  1370. -
  1371. - # Dice can't have zero or a negative number of sides.
  1372. - if dice_type <= 0:
  1373. - bot.reply("I don't have any dice with %d sides. =(" % dice_type)
  1374. - return None # Signal there was a problem
  1375. -
  1376. - # Can't roll a negative number of dice.
  1377. - if dice_num < 0:
  1378. - bot.reply("I'd rather not roll a negative amount of dice. =(")
  1379. - return None # Signal there was a problem
  1380. -
  1381. - # Upper limit for dice should be at most a million. Creating a dict with
  1382. - # more than a million elements already takes a noticeable amount of time
  1383. - # on a fast computer and ~55kB of memory.
  1384. - if dice_num > 1000:
  1385. - bot.reply('I only have 1000 dice. =(')
  1386. - return None # Signal there was a problem
  1387. -
  1388. - dice = DicePouch(dice_num, dice_type, 0)
  1389. -
  1390. - if result.group('drop_lowest'):
  1391. - drop = int(result.group('drop_lowest'))
  1392. - if drop >= 0:
  1393. - dice.drop_lowest(drop)
  1394. - else:
  1395. - bot.reply("I can't drop the lowest %d dice. =(" % drop)
  1396. -
  1397. - return dice
  1398. -
  1399. -
  1400. [email protected](".roll 3d1+1", 'You roll 3d1+1: (1+1+1)+1 = 4')
  1401. [email protected](".roll 3d1v2+1", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')
  1402. [email protected](".roll 2d4", 'You roll 2d4: \(\d\+\d\) = \d', re=True)
  1403. [email protected](".roll 100d1", '[^:]*: \(100x1\) = 100', re=True)
  1404. [email protected](".roll 1001d1", 'I only have 1000 dice. =(')
  1405. [email protected](".roll 1d1 + 1d1", 'You roll 1d1 + 1d1: (1) + (1) = 2')
  1406. [email protected](".roll 1d1+1d1", 'You roll 1d1+1d1: (1)+(1) = 2')
  1407. -def roll(bot, trigger):
  1408. - """.dice XdY[vZ][+N], rolls dice and reports the result.
  1409. -
  1410. - X is the number of dice. Y is the number of faces in the dice. Z is the
  1411. - number of lowest dice to be dropped from the result. N is the constant to
  1412. - be applied to the end result.
  1413. - """
  1414. - # This regexp is only allowed to have one captured group, because having
  1415. - # more would alter the output of re.findall.
  1416. - dice_regexp = r"-?\d*[dD]-?\d+(?:[vV]-?\d+)?"
  1417. -
  1418. - # Get a list of all dice expressions, evaluate them and then replace the
  1419. - # expressions in the original string with the results. Replacing is done
  1420. - # using string formatting, so %-characters must be escaped.
  1421. - if not trigger.group(2):
  1422. - return bot.reply("No dice to roll.")
  1423. - arg_str = trigger.group(2)
  1424. - dice_expressions = re.findall(dice_regexp, arg_str)
  1425. - arg_str = arg_str.replace("%", "%%")
  1426. - arg_str = re.sub(dice_regexp, "%s", arg_str)
  1427. -
  1428. - f = lambda dice_expr: _roll_dice(bot, dice_expr)
  1429. - dice = list(map(f, dice_expressions))
  1430. -
  1431. - if None in dice:
  1432. - # Stop computing roll if there was a problem rolling dice.
  1433. - return
  1434. -
  1435. - def _get_eval_str(dice):
  1436. - return "(%d)" % (dice.get_sum(),)
  1437. -
  1438. - def _get_pretty_str(dice):
  1439. - if dice.num <= 10:
  1440. - return dice.get_simple_string()
  1441. - elif dice.get_number_of_faces() <= 10:
  1442. - return dice.get_compressed_string()
  1443. - else:
  1444. - return "(...)"
  1445. -
  1446. - eval_str = arg_str % (tuple(map(_get_eval_str, dice)))
  1447. - pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))
  1448. -
  1449. - # Showing the actual error will hopefully give a better hint of what is
  1450. - # wrong with the syntax than a generic error message.
  1451. - try:
  1452. - result = eval_equation(eval_str)
  1453. - except Exception as e:
  1454. - bot.reply("SyntaxError, eval(%s), %s" % (eval_str, e))
  1455. - return
  1456. -
  1457. - bot.reply("You roll %s: %s = %d" % (
  1458. - trigger.group(2), pretty_str, result))
  1459. -
  1460. -
  1461. -def choose(bot, trigger):
  1462. - """
  1463. - .choice option1|option2|option3 - Makes a difficult choice easy.
  1464. - """
  1465. - if not trigger.group(2):
  1466. - return bot.reply('I\'d choose an option, but you didn\'t give me any.')
  1467. - choices = [trigger.group(2)]
  1468. - for delim in '|\\/,':
  1469. - choices = trigger.group(2).split(delim)
  1470. - if len(choices) > 1:
  1471. - break
  1472. - # Use a different delimiter in the output, to prevent ambiguity.
  1473. - for show_delim in ',|/\\':
  1474. - if show_delim not in trigger.group(2):
  1475. - show_delim += ' '
  1476. - break
  1477. -
  1478. - pick = random.choice(choices)
  1479. - return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))
  1480. -
  1481. -
  1482. -if __name__ == "__main__":
  1483. - from sopel.test_tools import run_example_tests
  1484. - run_example_tests(__file__)
  1485. diff --git a/sopel/modules/etymology.py b/sopel/modules/etymology.py
  1486. deleted file mode 100644
  1487. index e0663c8..0000000
  1488. --- a/sopel/modules/etymology.py
  1489. +++ /dev/null
  1490. @@ -1,97 +0,0 @@
  1491. -# coding=utf-8
  1492. -"""
  1493. -etymology.py - Sopel Etymology Module
  1494. -Copyright 2007-9, Sean B. Palmer, inamidst.com
  1495. -Licensed under the Eiffel Forum License 2.
  1496. -
  1497. -http://sopel.chat
  1498. -"""
  1499. -from __future__ import unicode_literals, absolute_import, print_function, division
  1500. -
  1501. -import re
  1502. -from sopel import web
  1503. -from sopel.module import commands, example, NOLIMIT
  1504. -
  1505. -etyuri = 'http://etymonline.com/?term=%s'
  1506. -etysearch = 'http://etymonline.com/?search=%s'
  1507. -
  1508. -r_definition = re.compile(r'(?ims)<dd[^>]*>.*?</dd>')
  1509. -r_tag = re.compile(r'<(?!!)[^>]+>')
  1510. -r_whitespace = re.compile(r'[\t\r\n ]+')
  1511. -
  1512. -abbrs = [
  1513. - 'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp',
  1514. - 'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk',
  1515. - '19c', '18c', '17c', '16c', 'St', 'Capt', 'obs', 'Jan', 'Feb', 'Mar',
  1516. - 'Apr', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'c', 'tr', 'e', 'g'
  1517. -]
  1518. -t_sentence = r'^.*?(?<!%s)(?:\.(?= [A-Z0-9]|\Z)|\Z)'
  1519. -r_sentence = re.compile(t_sentence % ')(?<!'.join(abbrs))
  1520. -
  1521. -
  1522. -def unescape(s):
  1523. - s = s.replace('&gt;', '>')
  1524. - s = s.replace('&lt;', '<')
  1525. - s = s.replace('&amp;', '&')
  1526. - return s
  1527. -
  1528. -
  1529. -def text(html):
  1530. - html = r_tag.sub('', html)
  1531. - html = r_whitespace.sub(' ', html)
  1532. - return unescape(html).strip()
  1533. -
  1534. -
  1535. -def etymology(word):
  1536. - # @@ <nsh> sbp, would it be possible to have a flag for .ety to get 2nd/etc
  1537. - # entries? - http://swhack.com/logs/2006-07-19#T15-05-29
  1538. -
  1539. - if len(word) > 25:
  1540. - raise ValueError("Word too long: %s[...]" % word[:10])
  1541. - word = {'axe': 'ax/axe'}.get(word, word)
  1542. -
  1543. - bytes = web.get(etyuri % word)
  1544. - definitions = r_definition.findall(bytes)
  1545. -
  1546. - if not definitions:
  1547. - return None
  1548. -
  1549. - defn = text(definitions[0])
  1550. - m = r_sentence.match(defn)
  1551. - if not m:
  1552. - return None
  1553. - sentence = m.group(0)
  1554. -
  1555. - maxlength = 275
  1556. - if len(sentence) > maxlength:
  1557. - sentence = sentence[:maxlength]
  1558. - words = sentence[:-5].split(' ')
  1559. - words.pop()
  1560. - sentence = ' '.join(words) + ' [...]'
  1561. -
  1562. - sentence = '"' + sentence.replace('"', "'") + '"'
  1563. - return sentence + ' - ' + (etyuri % word)
  1564. -
  1565. -
  1566. -@commands('ety')
  1567. -@example('.ety word')
  1568. -def f_etymology(bot, trigger):
  1569. - """Look up the etymology of a word"""
  1570. - word = trigger.group(2)
  1571. -
  1572. - try:
  1573. - result = etymology(word)
  1574. - except IOError:
  1575. - msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
  1576. - bot.msg(trigger.sender, msg)
  1577. - return NOLIMIT
  1578. - except (AttributeError, TypeError):
  1579. - result = None
  1580. -
  1581. - if result is not None:
  1582. - bot.msg(trigger.sender, result)
  1583. - else:
  1584. - uri = etysearch % word
  1585. - msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
  1586. - bot.msg(trigger.sender, msg)
  1587. - return NOLIMIT
  1588. diff --git a/sopel/modules/find.py b/sopel/modules/find.py
  1589. deleted file mode 100644
  1590. index 8a1f58a..0000000
  1591. --- a/sopel/modules/find.py
  1592. +++ /dev/null
  1593. @@ -1,139 +0,0 @@
  1594. -# coding=utf-8
  1595. -"""Sopel Spelling correction module
  1596. -
  1597. -This module will fix spelling errors if someone corrects them
  1598. -using the sed notation (s///) commonly found in vi/vim.
  1599. -"""
  1600. -# Copyright 2011, Michael Yanovich, yanovich.net
  1601. -# Copyright 2013, Elsie Powell, embolalia.com
  1602. -# Licensed under the Eiffel Forum License 2.
  1603. -# Contributions from: Matt Meinwald and Morgan Goose
  1604. -from __future__ import unicode_literals, absolute_import, print_function, division
  1605. -
  1606. -import re
  1607. -from sopel.tools import Identifier, SopelMemory
  1608. -from sopel.module import rule, priority
  1609. -from sopel.formatting import bold
  1610. -
  1611. -
  1612. -def setup(bot):
  1613. - bot.memory['find_lines'] = SopelMemory()
  1614. -
  1615. -
  1616. -@rule('.*')
  1617. -@priority('low')
  1618. -def collectlines(bot, trigger):
  1619. - """Create a temporary log of what people say"""
  1620. -
  1621. - # Don't log things in PM
  1622. - if trigger.is_privmsg:
  1623. - return
  1624. -
  1625. - # Add a log for the channel and nick, if there isn't already one
  1626. - if trigger.sender not in bot.memory['find_lines']:
  1627. - bot.memory['find_lines'][trigger.sender] = SopelMemory()
  1628. - if Identifier(trigger.nick) not in bot.memory['find_lines'][trigger.sender]:
  1629. - bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)] = list()
  1630. -
  1631. - # Create a temporary list of the user's lines in a channel
  1632. - templist = bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)]
  1633. - line = trigger.group()
  1634. - if line.startswith("s/"): # Don't remember substitutions
  1635. - return
  1636. - elif line.startswith("\x01ACTION"): # For /me messages
  1637. - line = line[:-1]
  1638. - templist.append(line)
  1639. - else:
  1640. - templist.append(line)
  1641. -
  1642. - del templist[:-10] # Keep the log to 10 lines per person
  1643. -
  1644. - bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)] = templist
  1645. -
  1646. -
  1647. -#Match nick, s/find/replace/flags. Flags and nick are optional, nick can be
  1648. -#followed by comma or colon, anything after the first space after the third
  1649. -#slash is ignored, you can escape slashes with backslashes, and if you want to
  1650. -#search for an actual backslash followed by an actual slash, you're shit out of
  1651. -#luck because this is the fucking regex of death as it is.
  1652. -@rule(r"""(?:
  1653. - (\S+) # Catch a nick in group 1
  1654. - [:,]\s+)? # Followed by colon/comma and whitespace, if given
  1655. - s/ # The literal s/
  1656. - ( # Group 2 is the thing to find
  1657. - (?:\\/ | [^/])+ # One or more non-slashes or escaped slashes
  1658. - )/( # Group 3 is what to replace with
  1659. - (?:\\/ | [^/])* # One or more non-slashes or escaped slashes
  1660. - )
  1661. - (?:/(\S+))? # Optional slash, followed by group 4 (flags)
  1662. - """)
  1663. -@priority('high')
  1664. -def findandreplace(bot, trigger):
  1665. - # Don't bother in PM
  1666. - if trigger.is_privmsg:
  1667. - return
  1668. -
  1669. - # Correcting other person vs self.
  1670. - rnick = Identifier(trigger.group(1) or trigger.nick)
  1671. -
  1672. - search_dict = bot.memory['find_lines']
  1673. - # only do something if there is conversation to work with
  1674. - if trigger.sender not in search_dict:
  1675. - return
  1676. - if Identifier(rnick) not in search_dict[trigger.sender]:
  1677. - return
  1678. -
  1679. - #TODO rest[0] is find, rest[1] is replace. These should be made variables of
  1680. - #their own at some point.
  1681. - rest = [trigger.group(2), trigger.group(3)]
  1682. - rest[0] = rest[0].replace(r'\/', '/')
  1683. - rest[1] = rest[1].replace(r'\/', '/')
  1684. - me = False # /me command
  1685. - flags = (trigger.group(4) or '')
  1686. -
  1687. - # If g flag is given, replace all. Otherwise, replace once.
  1688. - if 'g' in flags:
  1689. - count = -1
  1690. - else:
  1691. - count = 1
  1692. -
  1693. - # repl is a lambda function which performs the substitution. i flag turns
  1694. - # off case sensitivity. re.U turns on unicode replacement.
  1695. - if 'i' in flags:
  1696. - regex = re.compile(re.escape(rest[0]), re.U | re.I)
  1697. - repl = lambda s: re.sub(regex, rest[1], s, count == 1)
  1698. - else:
  1699. - repl = lambda s: s.replace(rest[0], rest[1], count)
  1700. -
  1701. - # Look back through the user's lines in the channel until you find a line
  1702. - # where the replacement works
  1703. - new_phrase = None
  1704. - for line in reversed(search_dict[trigger.sender][rnick]):
  1705. - if line.startswith("\x01ACTION"):
  1706. - me = True # /me command
  1707. - line = line[8:]
  1708. - else:
  1709. - me = False
  1710. - new_phrase = repl(line)
  1711. - if new_phrase != line: # we are done
  1712. - break
  1713. -
  1714. - if not new_phrase or new_phrase == line:
  1715. - return # Didn't find anything
  1716. -
  1717. - # Save the new "edited" message.
  1718. - action = (me and '\x01ACTION ') or '' # If /me message, prepend \x01ACTION
  1719. - templist = search_dict[trigger.sender][rnick]
  1720. - templist.append(action + new_phrase)
  1721. - search_dict[trigger.sender][rnick] = templist
  1722. - bot.memory['find_lines'] = search_dict
  1723. -
  1724. - # output
  1725. - if not me:
  1726. - new_phrase = '%s to say: %s' % (bold('meant'), new_phrase)
  1727. - if trigger.group(1):
  1728. - phrase = '%s thinks %s %s' % (trigger.nick, rnick, new_phrase)
  1729. - else:
  1730. - phrase = '%s %s' % (trigger.nick, new_phrase)
  1731. -
  1732. - bot.say(phrase)
  1733. diff --git a/sopel/modules/find_updates.py b/sopel/modules/find_updates.py
  1734. deleted file mode 100644
  1735. index b411447..0000000
  1736. --- a/sopel/modules/find_updates.py
  1737. +++ /dev/null
  1738. @@ -1,58 +0,0 @@
  1739. -# coding=utf-8
  1740. -"""Update checking module for Sopel.
  1741. -
  1742. -This is separated from version.py, so that it can be easily overridden by
  1743. -distribution packagers, and they can check their repositories rather than the
  1744. -Sopel website.
  1745. -"""
  1746. -# Copyright 2014, Elsie Powell, embolalia.com
  1747. -# Licensed under the Eiffel Forum License 2.
  1748. -from __future__ import unicode_literals, absolute_import, print_function, division
  1749. -
  1750. -import json
  1751. -
  1752. -import sopel
  1753. -import sopel.module
  1754. -import requests
  1755. -import sopel.tools
  1756. -
  1757. -wait_time = 24 * 60 * 60 # check once per day
  1758. -startup_check_run = False
  1759. -version_url = 'http://sopel.chat/latest.json'
  1760. -message = (
  1761. - 'A new Sopel version, {}, is available. I am running {}. Please update '
  1762. - 'me. Full release notes at {}'
  1763. -)
  1764. -unstable_message = (
  1765. - 'A new pre-release version, {}, is available. I am running {}. Please '
  1766. - 'update me. {}'
  1767. -)
  1768. -
  1769. -
  1770. [email protected](sopel.tools.events.RPL_LUSERCLIENT)
  1771. -def startup_version_check(bot, trigger):
  1772. - global startup_check_run
  1773. - if not startup_check_run:
  1774. - startup_check_run = True
  1775. - check_version(bot)
  1776. -
  1777. -
  1778. [email protected](wait_time)
  1779. -def check_version(bot):
  1780. - version = sopel.version_info
  1781. -
  1782. - info = requests.get(version_url).json()
  1783. - if version.releaselevel == 'final':
  1784. - latest = info['version']
  1785. - notes = info['release_notes']
  1786. - else:
  1787. - latest = info['unstable']
  1788. - notes = info.get('unstable_notes', '')
  1789. - if notes:
  1790. - notes = 'Full release notes at ' + notes
  1791. - latest_version = sopel._version_info(latest)
  1792. - msg = message.format(latest, sopel.__version__, notes)
  1793. -
  1794. - if version < latest_version:
  1795. - bot.msg(bot.config.core.owner, msg)
  1796. diff --git a/sopel/modules/help.py b/sopel/modules/help.py
  1797. deleted file mode 100644
  1798. index 1dadde4..0000000
  1799. --- a/sopel/modules/help.py
  1800. +++ /dev/null
  1801. @@ -1,72 +0,0 @@
  1802. -# coding=utf-8
  1803. -"""
  1804. -help.py - Sopel Help Module
  1805. -Copyright 2008, Sean B. Palmer, inamidst.com
  1806. -Copyright © 2013, Elad Alfassa, <[email protected]>
  1807. -Licensed under the Eiffel Forum License 2.
  1808. -
  1809. -http://sopel.chat
  1810. -"""
  1811. -from __future__ import unicode_literals, absolute_import, print_function, division
  1812. -
  1813. -import textwrap
  1814. -import collections
  1815. -
  1816. -from sopel.formatting import bold
  1817. -from sopel.module import commands, rule, example, priority
  1818. -
  1819. -
  1820. -@rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\?+)?$')
  1821. -@example('.help tell')
  1822. -@commands('help', 'commands')
  1823. -@priority('low')
  1824. -def help(bot, trigger):
  1825. - """Shows a command's documentation, and possibly an example."""
  1826. - if trigger.group(2):
  1827. - name = trigger.group(2)
  1828. - name = name.lower()
  1829. -
  1830. - # number of lines of help to show
  1831. - threshold = 3
  1832. -
  1833. - if name in bot.doc:
  1834. - if len(bot.doc[name][0]) + (1 if bot.doc[name][1] else 0) > threshold:
  1835. - if trigger.nick != trigger.sender: # don't say that if asked in private
  1836. - bot.reply('The documentation for this command is too long; I\'m sending it to you in a private message.')
  1837. - msgfun = lambda l: bot.msg(trigger.nick, l)
  1838. - else:
  1839. - msgfun = bot.reply
  1840. -
  1841. - for line in bot.doc[name][0]:
  1842. - msgfun(line)
  1843. - if bot.doc[name][1]:
  1844. - msgfun('e.g. ' + bot.doc[name][1])
  1845. - else:
  1846. - if not trigger.is_privmsg:
  1847. - bot.reply("I'm sending you a list of my commands in a private message!")
  1848. - bot.say(
  1849. - 'You can see more info about any of these commands by doing .help '
  1850. - '<command> (e.g. .help time)',
  1851. - trigger.nick
  1852. - )
  1853. -
  1854. - name_length = max(6, max(len(k) for k in bot.command_groups.keys()))
  1855. - for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():
  1856. - category = category.upper().ljust(name_length)
  1857. - cmds = ' '.join(cmds)
  1858. - msg = bold(category) + ' ' + cmds
  1859. - indent = ' ' * (name_length + 2)
  1860. - msg = textwrap.wrap(msg, subsequent_indent=indent)
  1861. - for line in msg:
  1862. - bot.say(line, trigger.nick)
  1863. -
  1864. -
  1865. -@rule('$nick' r'(?i)help(?:[?!]+)?$')
  1866. -@priority('low')
  1867. -def help2(bot, trigger):
  1868. - response = (
  1869. - 'Hi, I\'m a bot. Say ".commands" to me in private for a list ' +
  1870. - 'of my commands, or see http://sopel.chat for more ' +
  1871. - 'general details. My owner is %s.'
  1872. - ) % bot.config.core.owner
  1873. - bot.reply(response)
  1874. diff --git a/sopel/modules/ip.py b/sopel/modules/ip.py
  1875. deleted file mode 100644
  1876. index b2e2804..0000000
  1877. --- a/sopel/modules/ip.py
  1878. +++ /dev/null
  1879. @@ -1,139 +0,0 @@
  1880. -# coding=utf-8
  1881. -"""GeoIP lookup module"""
  1882. -# Copyright 2011, Dimitri Molenaars, TyRope.nl,
  1883. -# Copyright © 2013, Elad Alfassa <[email protected]>
  1884. -# Licensed under the Eiffel Forum License 2.
  1885. -
  1886. -from __future__ import unicode_literals, absolute_import, print_function, division
  1887. -
  1888. -import pygeoip
  1889. -import socket
  1890. -import os
  1891. -import gzip
  1892. -
  1893. -urlretrieve = None
  1894. -try:
  1895. - from urllib import urlretrieve
  1896. -except ImportError:
  1897. - try:
  1898. - # urlretrieve has been put under urllib.request in Python 3.
  1899. - # It's also deprecated so this should probably be replaced with
  1900. - # urllib2.
  1901. - from urllib.request import urlretrieve
  1902. - except ImportError:
  1903. - pass
  1904. -
  1905. -from sopel.config.types import StaticSection, FilenameAttribute
  1906. -from sopel.module import commands, example
  1907. -from sopel.logger import get_logger
  1908. -
  1909. -LOGGER = get_logger(__name__)
  1910. -
  1911. -
  1912. -class GeoipSection(StaticSection):
  1913. - GeoIP_db_path = FilenameAttribute('GeoIP_db_path', directory=True)
  1914. - """Path of the directory containing the GeoIP db files."""
  1915. -
  1916. -
  1917. -def configure(config):
  1918. - config.define_section('ip', GeoipSection)
  1919. - config.ip.configure_setting('GeoIP_db_path',
  1920. - 'Path of the GeoIP db files')
  1921. -
  1922. -
  1923. -def setup(bot=None):
  1924. - if not bot:
  1925. - return # Because of some weird pytest thing?
  1926. -
  1927. - bot.config.define_section('ip', GeoipSection)
  1928. -
  1929. -
  1930. -def _decompress(source, target, delete_after_decompression=True):
  1931. - """ Decompress a GZip file """
  1932. - f_in = gzip.open(source, 'rb')
  1933. - f_out = open(target, 'wb')
  1934. - f_out.writelines(f_in)
  1935. - f_out.close()
  1936. - f_in.close()
  1937. - if delete_after_decompression:
  1938. - os.remove(source)
  1939. -
  1940. -
  1941. -def _find_geoip_db(bot):
  1942. - """ Find the GeoIP database """
  1943. - config = bot.config
  1944. - if config.ip.GeoIP_db_path:
  1945. - cities_db = os.path.join(config.ip.GeoIP_db_path, 'GeoLiteCity.dat')
  1946. - ipasnum_db = os.path.join(config.ip.GeoIP_db_path, 'GeoIPASNum.dat')
  1947. - if os.path.isfile(cities_db) and os.path.isfile(ipasnum_db):
  1948. - return config.ip.GeoIP_db_path
  1949. - else:
  1950. - LOGGER.warning(
  1951. - 'GeoIP path configured but DB not found in configured path'
  1952. - )
  1953. - if (os.path.isfile(os.path.join(bot.config.core.homedir, 'GeoLiteCity.dat')) and
  1954. - os.path.isfile(os.path.join(bot.config.core.homedir, 'GeoIPASNum.dat'))):
  1955. - return bot.config.core.homedir
  1956. - elif (os.path.isfile(os.path.join('/usr/share/GeoIP', 'GeoLiteCity.dat')) and
  1957. - os.path.isfile(os.path.join('/usr/share/GeoIP', 'GeoIPASNum.dat'))):
  1958. - return '/usr/share/GeoIP'
  1959. - elif urlretrieve:
  1960. - LOGGER.warning('Downloading GeoIP database')
  1961. - bot.say('Downloading GeoIP database, please wait...')
  1962. - geolite_city_url = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'
  1963. - geolite_ASN_url = 'http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz'
  1964. - geolite_city_filepath = os.path.join(bot.config.core.homedir, 'GeoLiteCity.dat.gz')
  1965. - geolite_ASN_filepath = os.path.join(bot.config.core.homedir, 'GeoIPASNum.dat.gz')
  1966. - urlretrieve(geolite_city_url, geolite_city_filepath)
  1967. - urlretrieve(geolite_ASN_url, geolite_ASN_filepath)
  1968. - _decompress(geolite_city_filepath, geolite_city_filepath[:-3])
  1969. - _decompress(geolite_ASN_filepath, geolite_ASN_filepath[:-3])
  1970. - return bot.config.core.homedir
  1971. - else:
  1972. - return False
  1973. -
  1974. -
  1975. -@commands('iplookup', 'ip')
  1976. -@example('.ip 8.8.8.8',
  1977. - r'[IP/Host Lookup] Hostname: google-public-dns-a.google.com | Location: United States | Region: CA | ISP: AS15169 Google Inc.',
  1978. - re=True,
  1979. - ignore='Downloading GeoIP database, please wait...')
  1980. -def ip(bot, trigger):
  1981. - """IP Lookup tool"""
  1982. - if not trigger.group(2):
  1983. - return bot.reply("No search term.")
  1984. - query = trigger.group(2)
  1985. - db_path = _find_geoip_db(bot)
  1986. - if db_path is False:
  1987. - LOGGER.error('Can\'t find (or download) usable GeoIP database')
  1988. - bot.say('Sorry, I don\'t have a GeoIP database to use for this lookup')
  1989. - return False
  1990. - geolite_city_filepath = os.path.join(_find_geoip_db(bot), 'GeoLiteCity.dat')
  1991. - geolite_ASN_filepath = os.path.join(_find_geoip_db(bot), 'GeoIPASNum.dat')
  1992. - gi_city = pygeoip.GeoIP(geolite_city_filepath)
  1993. - gi_org = pygeoip.GeoIP(geolite_ASN_filepath)
  1994. - host = socket.getfqdn(query)
  1995. - response = "[IP/Host Lookup] Hostname: %s" % host
  1996. - try:
  1997. - response += " | Location: %s" % gi_city.country_name_by_name(query)
  1998. - except AttributeError:
  1999. - response += ' | Location: Unknown'
  2000. - except socket.gaierror:
  2001. - return bot.say('[IP/Host Lookup] Unable to resolve IP/Hostname')
  2002. -
  2003. - region_data = gi_city.region_by_name(query)
  2004. - try:
  2005. - region = region_data['region_code'] # pygeoip >= 0.3.0
  2006. - except KeyError:
  2007. - region = region_data['region_name'] # pygeoip < 0.3.0
  2008. - if region:
  2009. - response += " | Region: %s" % region
  2010. -
  2011. - isp = gi_org.org_by_name(query)
  2012. - response += " | ISP: %s" % isp
  2013. - bot.say(response)
  2014. -
  2015. -
  2016. -if __name__ == "__main__":
  2017. - from sopel.test_tools import run_example_tests
  2018. - run_example_tests(__file__)
  2019. diff --git a/sopel/modules/ipython.py b/sopel/modules/ipython.py
  2020. deleted file mode 100644
  2021. index 6df6112..0000000
  2022. --- a/sopel/modules/ipython.py
  2023. +++ /dev/null
  2024. @@ -1,78 +0,0 @@
  2025. -# coding=utf-8
  2026. -"""
  2027. -ipython.py - sopel ipython console!
  2028. -Copyright © 2014, Elad Alfassa <[email protected]>
  2029. -Licensed under the Eiffel Forum License 2.
  2030. -
  2031. -Sopel: http://sopel.chat/
  2032. -"""
  2033. -from __future__ import unicode_literals, absolute_import, print_function, division
  2034. -import sopel
  2035. -import sys
  2036. -if sys.version_info.major >= 3:
  2037. - # Backup stderr/stdout wrappers
  2038. - old_stdout = sys.stdout
  2039. - old_stderr = sys.stderr
  2040. -
  2041. - # IPython wants actual stderr and stdout. In Python 2, it only needed that
  2042. - # when actually starting the console, but in Python 3 it seems to need that
  2043. - # on import as well
  2044. - sys.stdout = sys.__stdout__
  2045. - sys.stderr = sys.__stderr__
  2046. -try:
  2047. - import IPython
  2048. - if hasattr(IPython, 'terminal'):
  2049. - from IPython.terminal.embed import InteractiveShellEmbed
  2050. - else:
  2051. - from IPython.frontend.terminal.embed import InteractiveShellEmbed
  2052. -finally:
  2053. - if sys.version_info.major >= 3:
  2054. - # Restore stderr/stdout wrappers
  2055. - sys.stdout = old_stdout
  2056. - sys.stderr = old_stderr
  2057. -
  2058. -console = None
  2059. -
  2060. -
  2061. [email protected]('console')
  2062. -def interactive_shell(bot, trigger):
  2063. - """
  2064. - Starts an interactive IPython console
  2065. - """
  2066. - global console
  2067. - if not trigger.admin:
  2068. - bot.say('Only admins can start the interactive console')
  2069. - return
  2070. - if 'iconsole_running' in bot.memory and bot.memory['iconsole_running']:
  2071. - bot.say('Console already running')
  2072. - return
  2073. - if not sys.__stdout__.isatty():
  2074. - bot.say('A tty is required to start the console')
  2075. - return
  2076. - if bot._daemon:
  2077. - bot.say('Can\'t start console when running as a daemon')
  2078. - return
  2079. -
  2080. - # Backup stderr/stdout wrappers
  2081. - old_stdout = sys.stdout
  2082. - old_stderr = sys.stderr
  2083. -
  2084. - # IPython wants actual stderr and stdout
  2085. - sys.stdout = sys.__stdout__
  2086. - sys.stderr = sys.__stderr__
  2087. -
  2088. - banner1 = 'Sopel interactive shell (embedded IPython)'
  2089. - banner2 = '`bot` and `trigger` are available. To exit, type exit'
  2090. - exitmsg = 'Interactive shell closed'
  2091. -
  2092. - console = InteractiveShellEmbed(banner1=banner1, banner2=banner2,
  2093. - exit_msg=exitmsg)
  2094. -
  2095. - bot.memory['iconsole_running'] = True
  2096. - bot.say('console started')
  2097. - console()
  2098. - bot.memory['iconsole_running'] = False
  2099. -
  2100. - # Restore stderr/stdout wrappers
  2101. - sys.stdout = old_stdout
  2102. - sys.stderr = old_stderr
  2103. diff --git a/sopel/modules/isup.py b/sopel/modules/isup.py
  2104. deleted file mode 100644
  2105. index d0c939d..0000000
  2106. --- a/sopel/modules/isup.py
  2107. +++ /dev/null
  2108. @@ -1,36 +0,0 @@
  2109. -# coding=utf-8
  2110. -"""Simple website status check with isup.me"""
  2111. -# Author: Elsie Powell http://embolalia.com
  2112. -from __future__ import unicode_literals, absolute_import, print_function, division
  2113. -
  2114. -from sopel import web
  2115. -from sopel.module import commands
  2116. -
  2117. -
  2118. -@commands('isup')
  2119. -def isup(bot, trigger):
  2120. - """isup.me website status checker"""
  2121. - site = trigger.group(2)
  2122. - if not site:
  2123. - return bot.reply("What site do you want to check?")
  2124. -
  2125. - if site[:7] != 'http://' and site[:8] != 'https://':
  2126. - if '://' in site:
  2127. - protocol = site.split('://')[0] + '://'
  2128. - return bot.reply("Try it again without the %s" % protocol)
  2129. - else:
  2130. - site = 'http://' + site
  2131. -
  2132. - if not '.' in site:
  2133. - site += ".com"
  2134. -
  2135. - try:
  2136. - response = web.get(site)
  2137. - except Exception:
  2138. - bot.say(site + ' looks down from here.')
  2139. - return
  2140. -
  2141. - if response:
  2142. - bot.say(site + ' looks fine to me.')
  2143. - else:
  2144. - bot.say(site + ' is down from here.')
  2145. diff --git a/sopel/modules/lmgtfy.py b/sopel/modules/lmgtfy.py
  2146. deleted file mode 100644
  2147. index 7d0caeb..0000000
  2148. --- a/sopel/modules/lmgtfy.py
  2149. +++ /dev/null
  2150. @@ -1,19 +0,0 @@
  2151. -# coding=utf-8
  2152. -"""
  2153. -lmgtfy.py - Sopel Let me Google that for you module
  2154. -Copyright 2013, Dimitri Molenaars http://tyrope.nl/
  2155. -Licensed under the Eiffel Forum License 2.
  2156. -
  2157. -http://sopel.chat/
  2158. -"""
  2159. -from __future__ import unicode_literals, absolute_import, print_function, division
  2160. -from sopel.module import commands
  2161. -
  2162. -
  2163. -@commands('lmgtfy', 'lmgify', 'gify', 'gtfy')
  2164. -def googleit(bot, trigger):
  2165. - """Let me just... google that for you."""
  2166. - #No input
  2167. - if not trigger.group(2):
  2168. - return bot.say('http://google.com/')
  2169. - bot.say('http://lmgtfy.com/?q=' + trigger.group(2).replace(' ', '+'))
  2170. diff --git a/sopel/modules/meetbot.py b/sopel/modules/meetbot.py
  2171. deleted file mode 100644
  2172. index f0c007f..0000000
  2173. --- a/sopel/modules/meetbot.py
  2174. +++ /dev/null
  2175. @@ -1,436 +0,0 @@
  2176. -# coding=utf-8
  2177. -"""
  2178. -meetbot.py - Sopel meeting logger module
  2179. -Copyright © 2012, Elad Alfassa, <[email protected]>
  2180. -Licensed under the Eiffel Forum License 2.
  2181. -
  2182. -This module is an attempt to implement at least some of the functionallity of Debian's meetbot
  2183. -"""
  2184. -from __future__ import unicode_literals, absolute_import, print_function, division
  2185. -import time
  2186. -import os
  2187. -from sopel.config.types import (
  2188. - StaticSection, FilenameAttribute, ValidatedAttribute
  2189. -)
  2190. -from sopel.web import quote
  2191. -from sopel.modules.url import find_title
  2192. -from sopel.module import example, commands, rule, priority
  2193. -from sopel.tools import Ddict, Identifier
  2194. -import codecs
  2195. -
  2196. -
  2197. -class MeetbotSection(StaticSection):
  2198. - meeting_log_path = FilenameAttribute('meeting_log_path', directory=True,
  2199. - default='~/www/meetings')
  2200. - """Path to meeting logs storage directory
  2201. -
  2202. - This should be an absolute path, accessible on a webserver."""
  2203. - meeting_log_baseurl = ValidatedAttribute(
  2204. - 'meeting_log_baseurl',
  2205. - default='http://localhost/~sopel/meetings'
  2206. - )
  2207. - """Base URL for the meeting logs directory"""
  2208. -
  2209. -
  2210. -def configure(config):
  2211. - config.define_section('meetbot', MeetbotSection)
  2212. - config.meetbot.configure_setting(
  2213. - 'meeting_log_path',
  2214. - 'Enter the directory to store logs in.'
  2215. - )
  2216. - config.meetbot.configure_setting(
  2217. - 'meeting_log_baseurl',
  2218. - 'Enter the base URL for the meeting logs.',
  2219. - )
  2220. -
  2221. -
  2222. -def setup(bot):
  2223. - bot.config.define_section('meetbot', MeetbotSection)
  2224. -
  2225. -
  2226. -meetings_dict = Ddict(dict) # Saves metadata about currently running meetings
  2227. -"""
  2228. -meetings_dict is a 2D dict.
  2229. -
  2230. -Each meeting should have:
  2231. -channel
  2232. -time of start
  2233. -head (can stop the meeting, plus all abilities of chairs)
  2234. -chairs (can add infolines to the logs)
  2235. -title
  2236. -current subject
  2237. -comments (what people who aren't voiced want to add)
  2238. -
  2239. -Using channel as the meeting ID as there can't be more than one meeting in a channel at the same time.
  2240. -"""
  2241. -meeting_log_path = '' # To be defined on meeting start as part of sanity checks, used by logging functions so we don't have to pass them bot
  2242. -meeting_log_baseurl = '' # To be defined on meeting start as part of sanity checks, used by logging functions so we don't have to pass them bot
  2243. -meeting_actions = {} # A dict of channels to the actions that have been created in them. This way we can have .listactions spit them back out later on.
  2244. -
  2245. -
  2246. -#Get the logfile name for the meeting in the requested channel
  2247. -#Used by all logging functions
  2248. -def figure_logfile_name(channel):
  2249. - if meetings_dict[channel]['title'] is 'Untitled meeting':
  2250. - name = 'untitled'
  2251. - else:
  2252. - name = meetings_dict[channel]['title']
  2253. - # Real simple sluggifying. This bunch of characters isn't exhaustive, but
  2254. - # whatever. It's close enough for most situations, I think.
  2255. - for c in ' ./\\:*?"<>|&*`':
  2256. - name = name.replace(c, '-')
  2257. - timestring = time.strftime('%Y-%m-%d-%H:%M', time.gmtime(meetings_dict[channel]['start']))
  2258. - filename = timestring + '_' + name
  2259. - return filename
  2260. -
  2261. -
  2262. -#Start HTML log
  2263. -def logHTML_start(channel):
  2264. - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
  2265. - timestring = time.strftime('%Y-%m-%d %H:%M', time.gmtime(meetings_dict[channel]['start']))
  2266. - title = '%s at %s, %s' % (meetings_dict[channel]['title'], channel, timestring)
  2267. - logfile.write('<!doctype html>\n<html>\n<head>\n<meta charset="utf-8">\n<title>%TITLE%</title>\n</head>\n<body>\n<h1>%TITLE%</h1>\n'.replace('%TITLE%', title))
  2268. - logfile.write('<h4>Meeting started by %s</h4><ul>\n' % meetings_dict[channel]['head'])
  2269. - logfile.close()
  2270. -
  2271. -
  2272. -#Write a list item in the HTML log
  2273. -def logHTML_listitem(item, channel):
  2274. - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
  2275. - logfile.write('<li>' + item + '</li>\n')
  2276. - logfile.close()
  2277. -
  2278. -
  2279. -#End the HTML log
  2280. -def logHTML_end(channel):
  2281. - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
  2282. - current_time = time.strftime('%H:%M:%S', time.gmtime())
  2283. - logfile.write('</ul>\n<h4>Meeting ended at %s UTC</h4>\n' % current_time)
  2284. - plainlog_url = meeting_log_baseurl + quote(channel + '/' + figure_logfile_name(channel) + '.log')
  2285. - logfile.write('<a href="%s">Full log</a>' % plainlog_url)
  2286. - logfile.write('\n</body>\n</html>')
  2287. - logfile.close()
  2288. -
  2289. -
  2290. -#Write a string to the plain text log
  2291. -def logplain(item, channel):
  2292. - current_time = time.strftime('%H:%M:%S', time.gmtime())
  2293. - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.log', 'a', encoding='utf-8')
  2294. - logfile.write('[' + current_time + '] ' + item + '\r\n')
  2295. - logfile.close()
  2296. -
  2297. -
  2298. -#Check if a meeting is currently running
  2299. -def ismeetingrunning(channel):
  2300. - try:
  2301. - if meetings_dict[channel]['running']:
  2302. - return True
  2303. - else:
  2304. - return False
  2305. - except:
  2306. - return False
  2307. -
  2308. -
  2309. -#Check if nick is a chair or head of the meeting
  2310. -def ischair(nick, channel):
  2311. - try:
  2312. - if nick.lower() == meetings_dict[channel]['head'] or nick.lower() in meetings_dict[channel]['chairs']:
  2313. - return True
  2314. - else:
  2315. - return False
  2316. - except:
  2317. - return False
  2318. -
  2319. -
  2320. -#Start meeting (also preforms all required sanity checks)
  2321. -@commands('startmeeting')
  2322. -@example('.startmeeting title or .startmeeting')
  2323. -def startmeeting(bot, trigger):
  2324. - """
  2325. - Start a meeting.
  2326. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2327. - """
  2328. - if ismeetingrunning(trigger.sender):
  2329. - bot.say('Can\'t do that, there is already a meeting in progress here!')
  2330. - return
  2331. - if trigger.is_privmsg:
  2332. - bot.say('Can only start meetings in channels')
  2333. - return
  2334. - #Start the meeting
  2335. - meetings_dict[trigger.sender]['start'] = time.time()
  2336. - if not trigger.group(2):
  2337. - meetings_dict[trigger.sender]['title'] = 'Untitled meeting'
  2338. - else:
  2339. - meetings_dict[trigger.sender]['title'] = trigger.group(2)
  2340. - meetings_dict[trigger.sender]['head'] = trigger.nick.lower()
  2341. - meetings_dict[trigger.sender]['running'] = True
  2342. - meetings_dict[trigger.sender]['comments'] = []
  2343. -
  2344. - global meeting_log_path
  2345. - meeting_log_path = bot.config.meetbot.meeting_log_path
  2346. - if not meeting_log_path.endswith('/'):
  2347. - meeting_log_path = meeting_log_path + '/'
  2348. - global meeting_log_baseurl
  2349. - meeting_log_baseurl = bot.config.meetbot.meeting_log_baseurl
  2350. - if not meeting_log_baseurl.endswith('/'):
  2351. - meeting_log_baseurl = meeting_log_baseurl + '/'
  2352. - if not os.path.isdir(meeting_log_path + trigger.sender):
  2353. - try:
  2354. - os.makedirs(meeting_log_path + trigger.sender)
  2355. - except Exception:
  2356. - bot.say("Can't create log directory for this channel, meeting not started!")
  2357. - meetings_dict[trigger.sender] = Ddict(dict)
  2358. - raise
  2359. - return
  2360. - #Okay, meeting started!
  2361. - logplain('Meeting started by ' + trigger.nick.lower(), trigger.sender)
  2362. - logHTML_start(trigger.sender)
  2363. - meeting_actions[trigger.sender] = []
  2364. - bot.say('Meeting started! use .action, .agreed, .info, .chairs, .subject and .comments to control the meeting. to end the meeting, type .endmeeting')
  2365. - bot.say('Users without speaking permission can use .comment ' +
  2366. - trigger.sender + ' followed by their comment in a PM with me to '
  2367. - 'vocalize themselves.')
  2368. -
  2369. -
  2370. -#Change the current subject (will appear as <h3> in the HTML log)
  2371. -@commands('subject')
  2372. -@example('.subject roll call')
  2373. -def meetingsubject(bot, trigger):
  2374. - """
  2375. - Change the meeting subject.
  2376. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2377. - """
  2378. - if not ismeetingrunning(trigger.sender):
  2379. - bot.say('Can\'t do that, start meeting first')
  2380. - return
  2381. - if not trigger.group(2):
  2382. - bot.say('what is the subject?')
  2383. - return
  2384. - if not ischair(trigger.nick, trigger.sender):
  2385. - bot.say('Only meeting head or chairs can do that')
  2386. - return
  2387. - meetings_dict[trigger.sender]['current_subject'] = trigger.group(2)
  2388. - logfile = codecs.open(meeting_log_path + trigger.sender + '/' + figure_logfile_name(trigger.sender) + '.html', 'a', encoding='utf-8')
  2389. - logfile.write('</ul><h3>' + trigger.group(2) + '</h3><ul>')
  2390. - logfile.close()
  2391. - logplain('Current subject: ' + trigger.group(2) + ', (set by ' + trigger.nick + ')', trigger.sender)
  2392. - bot.say('Current subject: ' + trigger.group(2))
  2393. -
  2394. -
  2395. -#End the meeting
  2396. -@commands('endmeeting')
  2397. -@example('.endmeeting')
  2398. -def endmeeting(bot, trigger):
  2399. - """
  2400. - End a meeting.
  2401. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2402. - """
  2403. - if not ismeetingrunning(trigger.sender):
  2404. - bot.say('Can\'t do that, start meeting first')
  2405. - return
  2406. - if not ischair(trigger.nick, trigger.sender):
  2407. - bot.say('Only meeting head or chairs can do that')
  2408. - return
  2409. - meeting_length = time.time() - meetings_dict[trigger.sender]['start']
  2410. - #TODO: Humanize time output
  2411. - bot.say("Meeting ended! total meeting length %d seconds" % meeting_length)
  2412. - logHTML_end(trigger.sender)
  2413. - htmllog_url = meeting_log_baseurl + quote(trigger.sender + '/' + figure_logfile_name(trigger.sender) + '.html')
  2414. - logplain('Meeting ended by %s, total meeting length %d seconds' % (trigger.nick, meeting_length), trigger.sender)
  2415. - bot.say('Meeting minutes: ' + htmllog_url)
  2416. - meetings_dict[trigger.sender] = Ddict(dict)
  2417. - del meeting_actions[trigger.sender]
  2418. -
  2419. -
  2420. -#Set meeting chairs (people who can control the meeting)
  2421. -@commands('chairs')
  2422. -@example('.chairs Tyrope Jason elad')
  2423. -def chairs(bot, trigger):
  2424. - """
  2425. - Set the meeting chairs.
  2426. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2427. - """
  2428. - if not ismeetingrunning(trigger.sender):
  2429. - bot.say('Can\'t do that, start meeting first')
  2430. - return
  2431. - if not trigger.group(2):
  2432. - bot.say('Who are the chairs?')
  2433. - return
  2434. - if trigger.nick.lower() == meetings_dict[trigger.sender]['head']:
  2435. - meetings_dict[trigger.sender]['chairs'] = trigger.group(2).lower().split(' ')
  2436. - chairs_readable = trigger.group(2).lower().replace(' ', ', ')
  2437. - logplain('Meeting chairs are: ' + chairs_readable, trigger.sender)
  2438. - logHTML_listitem('<span style="font-weight: bold">Meeting chairs are: </span>' + chairs_readable, trigger.sender)
  2439. - bot.say('Meeting chairs are: ' + chairs_readable)
  2440. - else:
  2441. - bot.say("Only meeting head can set chairs")
  2442. -
  2443. -
  2444. -#Log action item in the HTML log
  2445. -@commands('action')
  2446. -@example('.action elad will develop a meetbot')
  2447. -def meetingaction(bot, trigger):
  2448. - """
  2449. - Log an action in the meeting log
  2450. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2451. - """
  2452. - if not ismeetingrunning(trigger.sender):
  2453. - bot.say('Can\'t do that, start meeting first')
  2454. - return
  2455. - if not trigger.group(2):
  2456. - bot.say('try .action someone will do something')
  2457. - return
  2458. - if not ischair(trigger.nick, trigger.sender):
  2459. - bot.say('Only meeting head or chairs can do that')
  2460. - return
  2461. - logplain('ACTION: ' + trigger.group(2), trigger.sender)
  2462. - logHTML_listitem('<span style="font-weight: bold">Action: </span>' + trigger.group(2), trigger.sender)
  2463. - meeting_actions[trigger.sender].append(trigger.group(2))
  2464. - bot.say('ACTION: ' + trigger.group(2))
  2465. -
  2466. -
  2467. -@commands('listactions')
  2468. -@example('.listactions')
  2469. -def listactions(bot, trigger):
  2470. - if not ismeetingrunning(trigger.sender):
  2471. - bot.say('Can\'t do that, start meeting first')
  2472. - return
  2473. - for action in meeting_actions[trigger.sender]:
  2474. - bot.say('ACTION: ' + action)
  2475. -
  2476. -
  2477. -#Log agreed item in the HTML log
  2478. -@commands('agreed')
  2479. -@example('.agreed Bowties are cool')
  2480. -def meetingagreed(bot, trigger):
  2481. - """
  2482. - Log an agreement in the meeting log.
  2483. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2484. - """
  2485. - if not ismeetingrunning(trigger.sender):
  2486. - bot.say('Can\'t do that, start meeting first')
  2487. - return
  2488. - if not trigger.group(2):
  2489. - bot.say('try .action someone will do something')
  2490. - return
  2491. - if not ischair(trigger.nick, trigger.sender):
  2492. - bot.say('Only meeting head or chairs can do that')
  2493. - return
  2494. - logplain('AGREED: ' + trigger.group(2), trigger.sender)
  2495. - logHTML_listitem('<span style="font-weight: bold">Agreed: </span>' + trigger.group(2), trigger.sender)
  2496. - bot.say('AGREED: ' + trigger.group(2))
  2497. -
  2498. -
  2499. -#Log link item in the HTML log
  2500. -@commands('link')
  2501. -@example('.link http://example.com')
  2502. -def meetinglink(bot, trigger):
  2503. - """
  2504. - Log a link in the meeing log.
  2505. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2506. - """
  2507. - if not ismeetingrunning(trigger.sender):
  2508. - bot.say('Can\'t do that, start meeting first')
  2509. - return
  2510. - if not trigger.group(2):
  2511. - bot.say('try .action someone will do something')
  2512. - return
  2513. - if not ischair(trigger.nick, trigger.sender):
  2514. - bot.say('Only meeting head or chairs can do that')
  2515. - return
  2516. - link = trigger.group(2)
  2517. - if not link.startswith("http"):
  2518. - link = "http://" + link
  2519. - try:
  2520. - title = find_title(link)
  2521. - except:
  2522. - title = ''
  2523. - logplain('LINK: %s [%s]' % (link, title), trigger.sender)
  2524. - logHTML_listitem('<a href="%s">%s</a>' % (link, title), trigger.sender)
  2525. - bot.say('LINK: ' + link)
  2526. -
  2527. -
  2528. -#Log informational item in the HTML log
  2529. -@commands('info')
  2530. -@example('.info all board members present')
  2531. -def meetinginfo(bot, trigger):
  2532. - """
  2533. - Log an informational item in the meeting log
  2534. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2535. - """
  2536. - if not ismeetingrunning(trigger.sender):
  2537. - bot.say('Can\'t do that, start meeting first')
  2538. - return
  2539. - if not trigger.group(2):
  2540. - bot.say('try .info some informative thing')
  2541. - return
  2542. - if not ischair(trigger.nick, trigger.sender):
  2543. - bot.say('Only meeting head or chairs can do that')
  2544. - return
  2545. - logplain('INFO: ' + trigger.group(2), trigger.sender)
  2546. - logHTML_listitem(trigger.group(2), trigger.sender)
  2547. - bot.say('INFO: ' + trigger.group(2))
  2548. -
  2549. -
  2550. -#called for every single message
  2551. -#Will log to plain text only
  2552. -@rule('(.*)')
  2553. -@priority('low')
  2554. -def log_meeting(bot, trigger):
  2555. - if not ismeetingrunning(trigger.sender):
  2556. - return
  2557. - if trigger.startswith('.endmeeting') or trigger.startswith('.chairs') or trigger.startswith('.action') or trigger.startswith('.info') or trigger.startswith('.startmeeting') or trigger.startswith('.agreed') or trigger.startswith('.link') or trigger.startswith('.subject'):
  2558. - return
  2559. - logplain('<' + trigger.nick + '> ' + trigger, trigger.sender)
  2560. -
  2561. -
  2562. -@commands('comment')
  2563. -def take_comment(bot, trigger):
  2564. - """
  2565. - Log a comment, to be shown with other comments when a chair uses .comments.
  2566. - Intended to allow commentary from those outside the primary group of people
  2567. - in the meeting.
  2568. -
  2569. - Used in private message only, as `.comment <#channel> <comment to add>`
  2570. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2571. - """
  2572. - if not trigger.sender.is_nick():
  2573. - return
  2574. - if not trigger.group(4): # <2 arguements were given
  2575. - bot.say('Usage: .comment <#channel> <comment to add>')
  2576. - return
  2577. -
  2578. - target, message = trigger.group(2).split(None, 1)
  2579. - target = Identifier(target)
  2580. - if not ismeetingrunning(target):
  2581. - bot.say("There's not currently a meeting in that channel.")
  2582. - else:
  2583. - meetings_dict[trigger.group(3)]['comments'].append((trigger.nick, message))
  2584. - bot.say("Your comment has been recorded. It will be shown when the"
  2585. - " chairs tell me to show the comments.")
  2586. - bot.msg(meetings_dict[trigger.group(3)]['head'], "A new comment has been recorded.")
  2587. -
  2588. -
  2589. -@commands('comments')
  2590. -def show_comments(bot, trigger):
  2591. - """
  2592. - Show the comments that have been logged for this meeting with .comment.
  2593. - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
  2594. - """
  2595. - if not ismeetingrunning(trigger.sender):
  2596. - return
  2597. - if not ischair(trigger.nick, trigger.sender):
  2598. - bot.say('Only meeting head or chairs can do that')
  2599. - return
  2600. - comments = meetings_dict[trigger.sender]['comments']
  2601. - if comments:
  2602. - msg = 'The following comments were made:'
  2603. - bot.say(msg)
  2604. - logplain('<%s> %s' % (bot.nick, msg), trigger.sender)
  2605. - for comment in comments:
  2606. - msg = '<%s> %s' % comment
  2607. - bot.say(msg)
  2608. - logplain('<%s> %s' % (bot.nick, msg), trigger.sender)
  2609. - meetings_dict[trigger.sender]['comments'] = []
  2610. - else:
  2611. - bot.say('No comments have been logged.')
  2612. diff --git a/sopel/modules/movie.py b/sopel/modules/movie.py
  2613. deleted file mode 100644
  2614. index 8469f53..0000000
  2615. --- a/sopel/modules/movie.py
  2616. +++ /dev/null
  2617. @@ -1,48 +0,0 @@
  2618. -# coding=utf-8
  2619. -"""
  2620. -imdb.py - Sopel Movie Information Module
  2621. -Copyright © 2012-2013, Elad Alfassa, <[email protected]>
  2622. -Licensed under the Eiffel Forum License 2.
  2623. -
  2624. -This module relies on omdbapi.com
  2625. -"""
  2626. -from __future__ import unicode_literals, absolute_import, print_function, division
  2627. -import requests
  2628. -import sopel.module
  2629. -from sopel.logger import get_logger
  2630. -
  2631. -LOGGER = get_logger(__name__)
  2632. -
  2633. -
  2634. [email protected]('movie', 'imdb')
  2635. [email protected]('.movie ThisTitleDoesNotExist', '[MOVIE] Movie not found!')
  2636. [email protected]('.movie Citizen Kane', '[MOVIE] Title: Citizen Kane | Year: 1941 | Rating: 8.4 | Genre: Drama, Mystery | IMDB Link: http://imdb.com/title/tt0033467')
  2637. -def movie(bot, trigger):
  2638. - """
  2639. - Returns some information about a movie, like Title, Year, Rating, Genre and IMDB Link.
  2640. - """
  2641. - if not trigger.group(2):
  2642. - return
  2643. - word = trigger.group(2).rstrip()
  2644. - uri = "http://www.omdbapi.com/"
  2645. - data = requests.get(uri, params={'t': word}, timeout=30).json()
  2646. - if data['Response'] == 'False':
  2647. - if 'Error' in data:
  2648. - message = '[MOVIE] %s' % data['Error']
  2649. - else:
  2650. - LOGGER.warning(
  2651. - 'Got an error from the OMDb api, search phrase was %s; data was %s',
  2652. - word, str(data))
  2653. - message = '[MOVIE] Got an error from OMDbapi'
  2654. - else:
  2655. - message = '[MOVIE] Title: ' + data['Title'] + \
  2656. - ' | Year: ' + data['Year'] + \
  2657. - ' | Rating: ' + data['imdbRating'] + \
  2658. - ' | Genre: ' + data['Genre'] + \
  2659. - ' | IMDB Link: http://imdb.com/title/' + data['imdbID']
  2660. - bot.say(message)
  2661. -
  2662. -
  2663. -if __name__ == "__main__":
  2664. - from sopel.test_tools import run_example_tests
  2665. - run_example_tests(__file__)
  2666. diff --git a/sopel/modules/ping.py b/sopel/modules/ping.py
  2667. deleted file mode 100644
  2668. index ee9c3d2..0000000
  2669. --- a/sopel/modules/ping.py
  2670. +++ /dev/null
  2671. @@ -1,29 +0,0 @@
  2672. -# coding=utf-8
  2673. -"""
  2674. -ping.py - Sopel Ping Module
  2675. -Author: Sean B. Palmer, inamidst.com
  2676. -About: http://sopel.chat
  2677. -"""
  2678. -from __future__ import unicode_literals, absolute_import, print_function, division
  2679. -
  2680. -import random
  2681. -from sopel.module import rule, priority, thread
  2682. -
  2683. -
  2684. -@rule(r'(?i)(hi|hello|hey),? $nickname[ \t]*$')
  2685. -def hello(bot, trigger):
  2686. - greeting = random.choice(('Hi', 'Hey', 'Hello'))
  2687. - punctuation = random.choice(('', '!'))
  2688. - bot.say(greeting + ' ' + trigger.nick + punctuation)
  2689. -
  2690. -
  2691. -@rule(r'(?i)(Fuck|Screw) you,? $nickname[ \t]*$')
  2692. -def rude(bot, trigger):
  2693. - bot.say('Watch your mouth, ' + trigger.nick + ', or I\'ll tell your mother!')
  2694. -
  2695. -
  2696. -@rule('$nickname!')
  2697. -@priority('high')
  2698. -@thread(False)
  2699. -def interjection(bot, trigger):
  2700. - bot.say(trigger.nick + '!')
  2701. diff --git a/sopel/modules/rand.py b/sopel/modules/rand.py
  2702. deleted file mode 100644
  2703. index b9b0a6e..0000000
  2704. --- a/sopel/modules/rand.py
  2705. +++ /dev/null
  2706. @@ -1,49 +0,0 @@
  2707. -# coding=utf-8
  2708. -"""
  2709. -rand.py - Rand Module
  2710. -Copyright 2013, Ari Koivula, <[email protected]>
  2711. -Licensed under the Eiffel Forum License 2.
  2712. -
  2713. -http://sopel.chat
  2714. -"""
  2715. -from __future__ import unicode_literals, absolute_import, print_function, division
  2716. -
  2717. -from sopel.module import commands, example
  2718. -import random
  2719. -import sys
  2720. -
  2721. -
  2722. -@commands('rand')
  2723. -@example('.rand 2', r'random\(0, 2\) = (0|1|2)', re=True, repeat=10)
  2724. -@example('.rand -1 -1', 'random(-1, -1) = -1')
  2725. -@example('.rand', r'random\(0, \d+\) = \d+', re=True)
  2726. -@example('.rand 99 10', r'random\(10, 99\) = \d\d', re=True, repeat=10)
  2727. -@example('.rand 10 99', r'random\(10, 99\) = \d\d', re=True, repeat=10)
  2728. -def rand(bot, trigger):
  2729. - """Replies with a random number between first and second argument."""
  2730. - arg1 = trigger.group(3)
  2731. - arg2 = trigger.group(4)
  2732. -
  2733. - try:
  2734. - if arg2 is not None:
  2735. - low = int(arg1)
  2736. - high = int(arg2)
  2737. - elif arg1 is not None:
  2738. - low = 0
  2739. - high = int(arg1)
  2740. - else:
  2741. - low = 0
  2742. - high = sys.maxsize
  2743. - except (ValueError, TypeError):
  2744. - return bot.reply("Arguments must be of integer type")
  2745. -
  2746. - if low > high:
  2747. - low, high = high, low
  2748. -
  2749. - number = random.randint(low, high)
  2750. - bot.reply("random(%d, %d) = %d" % (low, high, number))
  2751. -
  2752. -
  2753. -if __name__ == "__main__":
  2754. - from sopel.test_tools import run_example_tests
  2755. - run_example_tests(__file__)
  2756. diff --git a/sopel/modules/reddit.py b/sopel/modules/reddit.py
  2757. deleted file mode 100644
  2758. index f5f79e0..0000000
  2759. --- a/sopel/modules/reddit.py
  2760. +++ /dev/null
  2761. @@ -1,191 +0,0 @@
  2762. -# coding=utf-8
  2763. -# Author: Elsie Powell, embolalia.com
  2764. -from __future__ import unicode_literals, absolute_import, print_function, division
  2765. -
  2766. -from sopel.module import commands, rule, example, require_chanmsg, NOLIMIT, OP
  2767. -from sopel.formatting import bold, color, colors
  2768. -from sopel.web import USER_AGENT
  2769. -from sopel.tools import SopelMemory, time
  2770. -import datetime as dt
  2771. -import praw
  2772. -import re
  2773. -import sys
  2774. -if sys.version_info.major >= 3:
  2775. - unicode = str
  2776. - if sys.version_info.minor >= 4:
  2777. - from html import unescape
  2778. - else:
  2779. - from html.parser import HTMLParser
  2780. - unescape = HTMLParser().unescape
  2781. -else:
  2782. - from HTMLParser import HTMLParser
  2783. - unescape = HTMLParser().unescape
  2784. -
  2785. -
  2786. -domain = r'https?://(?:www\.|np\.)?reddit\.com'
  2787. -post_url = '%s/r/.*?/comments/([\w-]+)' % domain
  2788. -user_url = '%s/u(ser)?/([\w-]+)' % domain
  2789. -post_regex = re.compile(post_url)
  2790. -user_regex = re.compile(user_url)
  2791. -
  2792. -
  2793. -def setup(bot):
  2794. - if not bot.memory.contains('url_callbacks'):
  2795. - bot.memory['url_callbacks'] = SopelMemory()
  2796. - bot.memory['url_callbacks'][post_regex] = rpost_info
  2797. - bot.memory['url_callbacks'][user_regex] = redditor_info
  2798. -
  2799. -
  2800. -def shutdown(bot):
  2801. - del bot.memory['url_callbacks'][post_regex]
  2802. - del bot.memory['url_callbacks'][user_regex]
  2803. -
  2804. -
  2805. -@rule('.*%s.*' % post_url)
  2806. -def rpost_info(bot, trigger, match=None):
  2807. - r = praw.Reddit(user_agent=USER_AGENT)
  2808. - match = match or trigger
  2809. - s = r.get_submission(submission_id=match.group(1))
  2810. -
  2811. - message = ('[REDDIT] {title} {link}{nsfw} | {points} points ({percent}) | '
  2812. - '{comments} comments | Posted by {author} | '
  2813. - 'Created at {created}')
  2814. -
  2815. - if s.is_self:
  2816. - link = '(self.{})'.format(s.subreddit.display_name)
  2817. - else:
  2818. - link = '({}) to r/{}'.format(s.url, s.subreddit.display_name)
  2819. -
  2820. - if s.over_18:
  2821. - nsfw = bold(color(' [NSFW]', colors.RED))
  2822. - sfw = bot.db.get_channel_value(trigger.sender, 'sfw')
  2823. - if sfw:
  2824. - link = '(link hidden)'
  2825. - bot.write(['KICK', trigger.sender, trigger.nick,
  2826. - 'Linking to NSFW content in a SFW channel.'])
  2827. - else:
  2828. - nsfw = ''
  2829. -
  2830. - if s.author:
  2831. - author = s.author.name
  2832. - else:
  2833. - author = '[deleted]'
  2834. -
  2835. - tz = time.get_timezone(bot.db, bot.config, None, trigger.nick,
  2836. - trigger.sender)
  2837. - time_created = dt.datetime.utcfromtimestamp(s.created_utc)
  2838. - created = time.format_time(bot.db, bot.config, tz, trigger.nick,
  2839. - trigger.sender, time_created)
  2840. -
  2841. - if s.score > 0:
  2842. - point_color = colors.GREEN
  2843. - else:
  2844. - point_color = colors.RED
  2845. -
  2846. - percent = color(unicode(s.upvote_ratio * 100) + '%', point_color)
  2847. -
  2848. - title = unescape(s.title)
  2849. - message = message.format(
  2850. - title=title, link=link, nsfw=nsfw, points=s.score, percent=percent,
  2851. - comments=s.num_comments, author=author, created=created)
  2852. -
  2853. - bot.say(message)
  2854. -
  2855. -
  2856. -# If you change this, you'll have to change some other things...
  2857. -@commands('redditor')
  2858. -@example('.redditor poem_for_your_sprog')
  2859. -def redditor_info(bot, trigger, match=None):
  2860. - """Show information about the given Redditor"""
  2861. - commanded = re.match(bot.config.core.prefix + 'redditor', trigger)
  2862. - r = praw.Reddit(user_agent=USER_AGENT)
  2863. - match = match or trigger
  2864. - try:
  2865. - u = r.get_redditor(match.group(2))
  2866. - except:
  2867. - if commanded:
  2868. - bot.say('No such Redditor.')
  2869. - return NOLIMIT
  2870. - else:
  2871. - return
  2872. - # Fail silently if it wasn't an explicit command.
  2873. -
  2874. - message = '[REDDITOR] ' + u.name
  2875. - now = dt.datetime.utcnow()
  2876. - cakeday_start = dt.datetime.utcfromtimestamp(u.created_utc)
  2877. - cakeday_start = cakeday_start.replace(year=now.year)
  2878. - day = dt.timedelta(days=1)
  2879. - year_div_by_400 = now.year % 400 == 0
  2880. - year_div_by_100 = now.year % 100 == 0
  2881. - year_div_by_4 = now.year % 4 == 0
  2882. - is_leap = year_div_by_400 or ((not year_div_by_100) and year_div_by_4)
  2883. - if (not is_leap) and ((cakeday_start.month, cakeday_start.day) == (2, 29)):
  2884. - # If cake day is 2/29 and it's not a leap year, cake day is 1/3.
  2885. - # Cake day begins at exact account creation time.
  2886. - is_cakeday = cakeday_start + day <= now <= cakeday_start + (2 * day)
  2887. - else:
  2888. - is_cakeday = cakeday_start <= now <= cakeday_start + day
  2889. -
  2890. - if is_cakeday:
  2891. - message = message + ' | 13Cake day'
  2892. - if commanded:
  2893. - message = message + ' | http://reddit.com/u/' + u.name
  2894. - if u.is_gold:
  2895. - message = message + ' | 08Gold'
  2896. - if u.is_mod:
  2897. - message = message + ' | 05Mod'
  2898. - message = message + (' | Link: ' + str(u.link_karma) + ' | Comment: '
  2899. - + str(u.comment_karma))
  2900. -
  2901. - bot.say(message)
  2902. -
  2903. -
  2904. -# If you change the groups here, you'll have to change some things above.
  2905. -@rule('.*%s.*' % user_url)
  2906. -def auto_redditor_info(bot, trigger):
  2907. - redditor_info(bot, trigger)
  2908. -
  2909. -
  2910. -@require_chanmsg('.setsfw is only permitted in channels')
  2911. -@commands('setsafeforwork', 'setsfw')
  2912. -@example('.setsfw true')
  2913. -@example('.setsfw false')
  2914. -def update_channel(bot, trigger):
  2915. - """
  2916. - Sets the Safe for Work status (true or false) for the current
  2917. - channel. Defaults to false.
  2918. - """
  2919. - if bot.privileges[trigger.sender][trigger.nick] < OP:
  2920. - return
  2921. - else:
  2922. - param = 'true'
  2923. - if trigger.group(2) and trigger.group(3):
  2924. - param = trigger.group(3).strip().lower()
  2925. - sfw = param == 'true'
  2926. - bot.db.set_channel_value(trigger.sender, 'sfw', sfw)
  2927. - if sfw:
  2928. - bot.reply('Got it. %s is now flagged as SFW.' % trigger.sender)
  2929. - else:
  2930. - bot.reply('Got it. %s is now flagged as NSFW.' % trigger.sender)
  2931. -
  2932. -
  2933. -@commands('getsafeforwork', 'getsfw')
  2934. -@example('.getsfw [channel]')
  2935. -def get_channel_sfw(bot, trigger):
  2936. - """
  2937. - Gets the preferred channel's Safe for Work status, or the current
  2938. - channel's status if no channel given.
  2939. - """
  2940. - channel = trigger.group(2)
  2941. - if not channel:
  2942. - channel = trigger.sender
  2943. - if channel.is_nick():
  2944. - return bot.say('.getsfw with no channel param is only permitted in channels')
  2945. -
  2946. - channel = channel.strip()
  2947. -
  2948. - sfw = bot.db.get_channel_value(channel, 'sfw')
  2949. - if sfw:
  2950. - bot.say('%s is flagged as SFW' % channel)
  2951. - else:
  2952. - bot.say('%s is flagged as NSFW' % channel)
  2953. diff --git a/sopel/modules/reload.py b/sopel/modules/reload.py
  2954. deleted file mode 100644
  2955. index 53bd720..0000000
  2956. --- a/sopel/modules/reload.py
  2957. +++ /dev/null
  2958. @@ -1,139 +0,0 @@
  2959. -# coding=utf-8
  2960. -"""
  2961. -reload.py - Sopel Module Reloader Module
  2962. -Copyright 2008, Sean B. Palmer, inamidst.com
  2963. -Licensed under the Eiffel Forum License 2.
  2964. -
  2965. -http://sopel.chat
  2966. -"""
  2967. -from __future__ import unicode_literals, absolute_import, print_function, division
  2968. -
  2969. -import collections
  2970. -import sys
  2971. -import time
  2972. -from sopel.tools import iteritems
  2973. -import sopel.loader
  2974. -import sopel.module
  2975. -import subprocess
  2976. -
  2977. -
  2978. [email protected]_commands("reload")
  2979. -def f_reload(bot, trigger):
  2980. - """Reloads a module, for use by admins only."""
  2981. - if not trigger.admin:
  2982. - return
  2983. -
  2984. - name = trigger.group(2)
  2985. -
  2986. - if not name or name == '*' or name.upper() == 'ALL THE THINGS':
  2987. - bot._callables = {
  2988. - 'high': collections.defaultdict(list),
  2989. - 'medium': collections.defaultdict(list),
  2990. - 'low': collections.defaultdict(list)
  2991. - }
  2992. - bot._command_groups = collections.defaultdict(list)
  2993. - bot.setup()
  2994. - return bot.reply('done')
  2995. -
  2996. - if name not in sys.modules:
  2997. - return bot.reply('%s: not loaded, try the `load` command' % name)
  2998. -
  2999. - old_module = sys.modules[name]
  3000. -
  3001. - old_callables = {}
  3002. - for obj_name, obj in iteritems(vars(old_module)):
  3003. - bot.unregister(obj)
  3004. -
  3005. - # Also remove all references to sopel callables from top level of the
  3006. - # module, so that they will not get loaded again if reloading the
  3007. - # module does not override them.
  3008. - for obj_name in old_callables.keys():
  3009. - delattr(old_module, obj_name)
  3010. -
  3011. - # Also delete the setup function
  3012. - if hasattr(old_module, "setup"):
  3013. - delattr(old_module, "setup")
  3014. -
  3015. - modules = sopel.loader.enumerate_modules(bot.config)
  3016. - path, type_ = modules[name]
  3017. - load_module(bot, name, path, type_)
  3018. -
  3019. -
  3020. -def load_module(bot, name, path, type_):
  3021. - module, mtime = sopel.loader.load_module(name, path, type_)
  3022. - relevant_parts = sopel.loader.clean_module(module, bot.config)
  3023. -
  3024. - bot.register(*relevant_parts)
  3025. -
  3026. - # TODO sys.modules[name] = module
  3027. - if hasattr(module, 'setup'):
  3028. - module.setup(bot)
  3029. -
  3030. - modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime))
  3031. -
  3032. - bot.reply('%r (version: %s)' % (module, modified))
  3033. -
  3034. -
  3035. [email protected]_commands('update')
  3036. -def f_update(bot, trigger):
  3037. - if not trigger.admin:
  3038. - return
  3039. -
  3040. - """Pulls the latest versions of all modules from Git"""
  3041. - proc = subprocess.Popen('/usr/bin/git pull',
  3042. - stdout=subprocess.PIPE,
  3043. - stderr=subprocess.PIPE, shell=True)
  3044. - bot.reply(proc.communicate()[0])
  3045. -
  3046. - f_reload(bot, trigger)
  3047. -
  3048. -
  3049. [email protected]_commands("load")
  3050. -def f_load(bot, trigger):
  3051. - """Loads a module, for use by admins only."""
  3052. - if not trigger.admin:
  3053. - return
  3054. -
  3055. - name = trigger.group(2)
  3056. - path = ''
  3057. - if not name:
  3058. - return bot.reply('Load what?')
  3059. -
  3060. - if name in sys.modules:
  3061. - return bot.reply('Module already loaded, use reload')
  3062. -
  3063. - mods = sopel.loader.enumerate_modules(bot.config)
  3064. - if name not in mods:
  3065. - return bot.reply('Module %s not found' % name)
  3066. - path, type_ = mods[name]
  3067. - load_module(bot, name, path, type_)
  3068. -
  3069. -
  3070. -# Catch PM based messages
  3071. -def pm_f_reload(bot, trigger):
  3072. - """Wrapper for allowing delivery of .reload command via PM"""
  3073. - if trigger.is_privmsg:
  3074. - f_reload(bot, trigger)
  3075. -
  3076. -
  3077. -def pm_f_update(bot, trigger):
  3078. - """Wrapper for allowing delivery of .update command via PM"""
  3079. - if trigger.is_privmsg:
  3080. - f_update(bot, trigger)
  3081. -
  3082. -
  3083. -def pm_f_load(bot, trigger):
  3084. - """Wrapper for allowing delivery of .load command via PM"""
  3085. - if trigger.is_privmsg:
  3086. - f_load(bot, trigger)
  3087. diff --git a/sopel/modules/remind.py b/sopel/modules/remind.py
  3088. deleted file mode 100644
  3089. index 7f59a57..0000000
  3090. --- a/sopel/modules/remind.py
  3091. +++ /dev/null
  3092. @@ -1,228 +0,0 @@
  3093. -# coding=utf-8
  3094. -"""
  3095. -remind.py - Sopel Reminder Module
  3096. -Copyright 2011, Sean B. Palmer, inamidst.com
  3097. -Licensed under the Eiffel Forum License 2.
  3098. -
  3099. -http://sopel.chat
  3100. -"""
  3101. -from __future__ import unicode_literals, absolute_import, print_function, division
  3102. -
  3103. -import os
  3104. -import re
  3105. -import time
  3106. -import threading
  3107. -import collections
  3108. -import codecs
  3109. -from datetime import datetime
  3110. -from sopel.module import commands, example, NOLIMIT
  3111. -import sopel.tools
  3112. -from sopel.tools.time import get_timezone, format_time
  3113. -
  3114. -try:
  3115. - import pytz
  3116. -except:
  3117. - pytz = None
  3118. -
  3119. -
  3120. -def filename(self):
  3121. - name = self.nick + '-' + self.config.core.host + '.reminders.db'
  3122. - return os.path.join(self.config.core.homedir, name)
  3123. -
  3124. -
  3125. -def load_database(name):
  3126. - data = {}
  3127. - if os.path.isfile(name):
  3128. - f = codecs.open(name, 'r', encoding='utf-8')
  3129. - for line in f:
  3130. - unixtime, channel, nick, message = line.split('\t')
  3131. - message = message.rstrip('\n')
  3132. - t = int(float(unixtime)) # WTFs going on here?
  3133. - reminder = (channel, nick, message)
  3134. - try:
  3135. - data[t].append(reminder)
  3136. - except KeyError:
  3137. - data[t] = [reminder]
  3138. - f.close()
  3139. - return data
  3140. -
  3141. -
  3142. -def dump_database(name, data):
  3143. - f = codecs.open(name, 'w', encoding='utf-8')
  3144. - for unixtime, reminders in sopel.tools.iteritems(data):
  3145. - for channel, nick, message in reminders:
  3146. - f.write('%s\t%s\t%s\t%s\n' % (unixtime, channel, nick, message))
  3147. - f.close()
  3148. -
  3149. -
  3150. -def setup(bot):
  3151. - bot.rfn = filename(bot)
  3152. - bot.rdb = load_database(bot.rfn)
  3153. -
  3154. - def monitor(bot):
  3155. - time.sleep(5)
  3156. - while True:
  3157. - now = int(time.time())
  3158. - unixtimes = [int(key) for key in bot.rdb]
  3159. - oldtimes = [t for t in unixtimes if t <= now]
  3160. - if oldtimes:
  3161. - for oldtime in oldtimes:
  3162. - for (channel, nick, message) in bot.rdb[oldtime]:
  3163. - if message:
  3164. - bot.msg(channel, nick + ': ' + message)
  3165. - else:
  3166. - bot.msg(channel, nick + '!')
  3167. - del bot.rdb[oldtime]
  3168. - dump_database(bot.rfn, bot.rdb)
  3169. - time.sleep(2.5)
  3170. -
  3171. - targs = (bot,)
  3172. - t = threading.Thread(target=monitor, args=targs)
  3173. - t.start()
  3174. -
  3175. -scaling = collections.OrderedDict([
  3176. - ('years', 365.25 * 24 * 3600),
  3177. - ('year', 365.25 * 24 * 3600),
  3178. - ('yrs', 365.25 * 24 * 3600),
  3179. - ('y', 365.25 * 24 * 3600),
  3180. -
  3181. - ('months', 29.53059 * 24 * 3600),
  3182. - ('month', 29.53059 * 24 * 3600),
  3183. - ('mo', 29.53059 * 24 * 3600),
  3184. -
  3185. - ('weeks', 7 * 24 * 3600),
  3186. - ('week', 7 * 24 * 3600),
  3187. - ('wks', 7 * 24 * 3600),
  3188. - ('wk', 7 * 24 * 3600),
  3189. - ('w', 7 * 24 * 3600),
  3190. -
  3191. - ('days', 24 * 3600),
  3192. - ('day', 24 * 3600),
  3193. - ('d', 24 * 3600),
  3194. -
  3195. - ('hours', 3600),
  3196. - ('hour', 3600),
  3197. - ('hrs', 3600),
  3198. - ('hr', 3600),
  3199. - ('h', 3600),
  3200. -
  3201. - ('minutes', 60),
  3202. - ('minute', 60),
  3203. - ('mins', 60),
  3204. - ('min', 60),
  3205. - ('m', 60),
  3206. -
  3207. - ('seconds', 1),
  3208. - ('second', 1),
  3209. - ('secs', 1),
  3210. - ('sec', 1),
  3211. - ('s', 1),
  3212. -])
  3213. -
  3214. -periods = '|'.join(scaling.keys())
  3215. -
  3216. -
  3217. -@commands('in')
  3218. -@example('.in 3h45m Go to class')
  3219. -def remind(bot, trigger):
  3220. - """Gives you a reminder in the given amount of time."""
  3221. - if not trigger.group(2):
  3222. - bot.say("Missing arguments for reminder command.")
  3223. - return NOLIMIT
  3224. - if trigger.group(3) and not trigger.group(4):
  3225. - bot.say("No message given for reminder.")
  3226. - return NOLIMIT
  3227. - duration = 0
  3228. - message = filter(None, re.split('(\d+(?:\.\d+)? ?(?:(?i)' + periods + ')) ?',
  3229. - trigger.group(2))[1:])
  3230. - reminder = ''
  3231. - stop = False
  3232. - for piece in message:
  3233. - grp = re.match('(\d+(?:\.\d+)?) ?(.*) ?', piece)
  3234. - if grp and not stop:
  3235. - length = float(grp.group(1))
  3236. - factor = scaling.get(grp.group(2).lower(), 60)
  3237. - duration += length * factor
  3238. - else:
  3239. - reminder = reminder + piece
  3240. - stop = True
  3241. - if duration == 0:
  3242. - return bot.reply("Sorry, didn't understand the input.")
  3243. -
  3244. - if duration % 1:
  3245. - duration = int(duration) + 1
  3246. - else:
  3247. - duration = int(duration)
  3248. - timezone = get_timezone(
  3249. - bot.db, bot.config, None, trigger.nick, trigger.sender)
  3250. - create_reminder(bot, trigger, duration, reminder, timezone)
  3251. -
  3252. -
  3253. -@commands('at')
  3254. -@example('.at 13:47 Do your homework!')
  3255. -def at(bot, trigger):
  3256. - """
  3257. - Gives you a reminder at the given time. Takes hh:mm:ssTimezone
  3258. - message. Timezone is any timezone Sopel takes elsewhere; the best choices
  3259. - are those from the tzdb; a list of valid options is available at
  3260. - http://sopel.chat/tz . The seconds and timezone are optional.
  3261. - """
  3262. - if not trigger.group(2):
  3263. - bot.say("No arguments given for reminder command.")
  3264. - return NOLIMIT
  3265. - if trigger.group(3) and not trigger.group(4):
  3266. - bot.say("No message given for reminder.")
  3267. - return NOLIMIT
  3268. - regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
  3269. - match = regex.match(trigger.group(2))
  3270. - if not match:
  3271. - bot.reply("Sorry, but I didn't understand your input.")
  3272. - return NOLIMIT
  3273. - hour, minute, second, tz, message = match.groups()
  3274. - if not second:
  3275. - second = '0'
  3276. -
  3277. - if pytz:
  3278. - timezone = get_timezone(bot.db, bot.config, tz,
  3279. - trigger.nick, trigger.sender)
  3280. - if not timezone:
  3281. - timezone = 'UTC'
  3282. - now = datetime.now(pytz.timezone(timezone))
  3283. - at_time = datetime(now.year, now.month, now.day,
  3284. - int(hour), int(minute), int(second),
  3285. - tzinfo=now.tzinfo)
  3286. - timediff = at_time - now
  3287. - else:
  3288. - if tz and tz.upper() != 'UTC':
  3289. - bot.reply("I don't have timzeone support installed.")
  3290. - return NOLIMIT
  3291. - now = datetime.now()
  3292. - at_time = datetime(now.year, now.month, now.day,
  3293. - int(hour), int(minute), int(second))
  3294. - timediff = at_time - now
  3295. -
  3296. - duration = timediff.seconds
  3297. -
  3298. - if duration < 0:
  3299. - duration += 86400
  3300. - create_reminder(bot, trigger, duration, message, 'UTC')
  3301. -
  3302. -
  3303. -def create_reminder(bot, trigger, duration, message, tz):
  3304. - t = int(time.time()) + duration
  3305. - reminder = (trigger.sender, trigger.nick, message)
  3306. - try:
  3307. - bot.rdb[t].append(reminder)
  3308. - except KeyError:
  3309. - bot.rdb[t] = [reminder]
  3310. -
  3311. - dump_database(bot.rfn, bot.rdb)
  3312. -
  3313. - if duration >= 60:
  3314. - remind_at = datetime.utcfromtimestamp(t)
  3315. - timef = format_time(bot.db, bot.config, tz, trigger.nick,
  3316. - trigger.sender, remind_at)
  3317. -
  3318. - bot.reply('Okay, will remind at %s' % timef)
  3319. - else:
  3320. - bot.reply('Okay, will remind in %s secs' % duration)
  3321. diff --git a/sopel/modules/safety.py b/sopel/modules/safety.py
  3322. deleted file mode 100644
  3323. index 17c850d..0000000
  3324. --- a/sopel/modules/safety.py
  3325. +++ /dev/null
  3326. @@ -1,197 +0,0 @@
  3327. -# coding=utf-8
  3328. -"""
  3329. -safety.py - Alerts about malicious URLs
  3330. -Copyright © 2014, Elad Alfassa, <[email protected]>
  3331. -Licensed under the Eiffel Forum License 2.
  3332. -
  3333. -This module uses virustotal.com
  3334. -"""
  3335. -from __future__ import unicode_literals, absolute_import, print_function, division
  3336. -
  3337. -import sopel.web as web
  3338. -from sopel.config.types import StaticSection, ValidatedAttribute, ListAttribute
  3339. -from sopel.formatting import color, bold
  3340. -from sopel.logger import get_logger
  3341. -from sopel.module import OP
  3342. -import sopel.tools
  3343. -import sys
  3344. -import json
  3345. -import time
  3346. -import os.path
  3347. -import re
  3348. -
  3349. -if sys.version_info.major > 2:
  3350. - unicode = str
  3351. - from urllib.request import urlretrieve
  3352. - from urllib.parse import urlparse
  3353. -else:
  3354. - from urllib import urlretrieve
  3355. - from urlparse import urlparse
  3356. -
  3357. -LOGGER = get_logger(__name__)
  3358. -
  3359. -vt_base_api_url = 'https://www.virustotal.com/vtapi/v2/url/'
  3360. -malware_domains = set()
  3361. -known_good = []
  3362. -
  3363. -
  3364. -class SafetySection(StaticSection):
  3365. - enabled_by_default = ValidatedAttribute('enabled_by_default', bool, default=True)
  3366. - """Enable URL safety in all channels where it isn't explicitly disabled."""
  3367. - known_good = ListAttribute('known_good')
  3368. - """List of "known good" domains to ignore."""
  3369. - vt_api_key = ValidatedAttribute('vt_api_key')
  3370. - """Optional VirusTotal API key."""
  3371. -
  3372. -
  3373. -def configure(config):
  3374. - config.define_section('safety', SafetySection)
  3375. - config.safety.configure_setting(
  3376. - 'enabled_by_default',
  3377. - "Enable URL safety in channels that don't specifically disable it?",
  3378. - )
  3379. - config.safety.configure_setting(
  3380. - 'known_good',
  3381. - 'Enter any domains to whitelist',
  3382. - )
  3383. - config.safety.configure_setting(
  3384. - 'vt_api_key',
  3385. - "Optionally, enter a VirusTotal API key to improve malicious URL "
  3386. - "protection.\nOtherwise, only the Malwarebytes DB will be used."
  3387. - )
  3388. -
  3389. -
  3390. -def setup(bot):
  3391. - bot.config.define_section('safety', SafetySection)
  3392. -
  3393. - bot.memory['safety_cache'] = sopel.tools.SopelMemory()
  3394. - for item in bot.config.safety.known_good:
  3395. - known_good.append(re.compile(item, re.I))
  3396. -
  3397. - loc = os.path.join(bot.config.homedir, 'malwaredomains.txt')
  3398. - if os.path.isfile(loc):
  3399. - if os.path.getmtime(loc) < time.time() - 24 * 60 * 60 * 7:
  3400. - # File exists but older than one week, update
  3401. - _download_malwaredomains_db(loc)
  3402. - else:
  3403. - _download_malwaredomains_db(loc)
  3404. - with open(loc, 'r') as f:
  3405. - for line in f:
  3406. - clean_line = unicode(line).strip().lower()
  3407. - if clean_line != '':
  3408. - malware_domains.add(clean_line)
  3409. -
  3410. -
  3411. -def _download_malwaredomains_db(path):
  3412. - print('Downloading malwaredomains db...')
  3413. - urlretrieve('http://mirror1.malwaredomains.com/files/justdomains', path)
  3414. -
  3415. -
  3416. [email protected]('(?u).*(https?://\S+).*')
  3417. -def url_handler(bot, trigger):
  3418. - """ Check for malicious URLs """
  3419. - check = True # Enable URL checking
  3420. - strict = False # Strict mode: kick on malicious URL
  3421. - positives = 0 # Number of engines saying it's malicious
  3422. - total = 0 # Number of total engines
  3423. - use_vt = True # Use VirusTotal
  3424. - check = bot.config.safety.enabled_by_default
  3425. - if check is None:
  3426. - # If not set, assume default
  3427. - check = True
  3428. - # DB overrides config:
  3429. - setting = bot.db.get_channel_value(trigger.sender, 'safety')
  3430. - if setting is not None:
  3431. - if setting == 'off':
  3432. - return # Not checking
  3433. - elif setting in ['on', 'strict', 'local', 'local strict']:
  3434. - check = True
  3435. - if setting == 'strict' or setting == 'local strict':
  3436. - strict = True
  3437. - if setting == 'local' or setting == 'local strict':
  3438. - use_vt = False
  3439. -
  3440. - if not check:
  3441. - return # Not overriden by DB, configured default off
  3442. -
  3443. - netloc = urlparse(trigger.group(1)).netloc
  3444. - if any(regex.search(netloc) for regex in known_good):
  3445. - return # Whitelisted
  3446. -
  3447. - apikey = bot.config.safety.vt_api_key
  3448. - try:
  3449. - if apikey is not None and use_vt:
  3450. - payload = {'resource': unicode(trigger),
  3451. - 'apikey': apikey,
  3452. - 'scan': '1'}
  3453. -
  3454. - if trigger not in bot.memory['safety_cache']:
  3455. - result = web.post(vt_base_api_url + 'report', payload)
  3456. - if sys.version_info.major > 2:
  3457. - result = result.decode('utf-8')
  3458. - result = json.loads(result)
  3459. - age = time.time()
  3460. - data = {'positives': result['positives'],
  3461. - 'total': result['total'],
  3462. - 'age': age}
  3463. - bot.memory['safety_cache'][trigger] = data
  3464. - if len(bot.memory['safety_cache']) > 1024:
  3465. - _clean_cache(bot)
  3466. - else:
  3467. - print('using cache')
  3468. - result = bot.memory['safety_cache'][trigger]
  3469. - positives = result['positives']
  3470. - total = result['total']
  3471. - except Exception:
  3472. - LOGGER.debug('Error from checking URL with VT.', exc_info=True)
  3473. - pass # Ignoring exceptions with VT so MalwareDomains will always work
  3474. -
  3475. - if unicode(netloc).lower() in malware_domains:
  3476. - # malwaredomains is more trustworthy than some VT engines
  3477. - # therefor it gets a weight of 10 engines when calculating confidence
  3478. - positives += 10
  3479. - total += 10
  3480. -
  3481. - if positives > 1:
  3482. - # Possibly malicious URL detected!
  3483. - confidence = '{}%'.format(round((positives / total) * 100))
  3484. - msg = 'link posted by %s is possibly malicious ' % bold(trigger.nick)
  3485. - msg += '(confidence %s - %s/%s)' % (confidence, positives, total)
  3486. - bot.say('[' + bold(color('WARNING', 'red')) + '] ' + msg)
  3487. - if strict:
  3488. - bot.write(['KICK', trigger.sender, trigger.nick,
  3489. - 'Posted a malicious link'])
  3490. -
  3491. -
  3492. -def toggle_safety(bot, trigger):
  3493. - """ Set safety setting for channel """
  3494. - if not trigger.admin and bot.privileges[trigger.sender][trigger.nick] < OP:
  3495. - bot.reply('Only channel operators can change safety settings')
  3496. - return
  3497. - allowed_states = ['strict', 'on', 'off', 'local', 'local strict']
  3498. - if not trigger.group(2) or trigger.group(2).lower() not in allowed_states:
  3499. - options = ' / '.join(allowed_states)
  3500. - bot.reply('Available options: %s' % options)
  3501. - return
  3502. -
  3503. - channel = trigger.sender.lower()
  3504. - bot.db.set_channel_value(channel, 'safety', trigger.group(2).lower())
  3505. - bot.reply('Safety is now set to "%s" on this channel' % trigger.group(2))
  3506. -
  3507. -
  3508. -# Clean the cache every day, also when > 1024 entries
  3509. [email protected](24 * 60 * 60)
  3510. -def _clean_cache(bot):
  3511. - """ Cleanup old entries in URL cache """
  3512. - # TODO probably should be using locks here, to make sure stuff doesn't
  3513. - # explode
  3514. - oldest_key_age = 0
  3515. - oldest_key = ''
  3516. - for key, data in sopel.tools.iteritems(bot.memory['safety_cache']):
  3517. - if data['age'] > oldest_key_age:
  3518. - oldest_key_age = data['age']
  3519. - oldest_key = key
  3520. - if oldest_key in bot.memory['safety_cache']:
  3521. - del bot.memory['safety_cache'][oldest_key]
  3522. diff --git a/sopel/modules/search.py b/sopel/modules/search.py
  3523. deleted file mode 100644
  3524. index e77d5db..0000000
  3525. --- a/sopel/modules/search.py
  3526. +++ /dev/null
  3527. @@ -1,128 +0,0 @@
  3528. -# coding=utf-8
  3529. -# Copyright 2008-9, Sean B. Palmer, inamidst.com
  3530. -# Copyright 2012, Elsie Powell, embolalia.com
  3531. -# Licensed under the Eiffel Forum License 2.
  3532. -from __future__ import unicode_literals, absolute_import, print_function, division
  3533. -
  3534. -import re
  3535. -from sopel import web
  3536. -from sopel.module import commands, example
  3537. -import json
  3538. -import sys
  3539. -
  3540. -if sys.version_info.major < 3:
  3541. - from urllib import quote_plus
  3542. -else:
  3543. - from urllib.parse import quote_plus
  3544. -
  3545. -
  3546. -def formatnumber(n):
  3547. - """Format a number with beautiful commas."""
  3548. - parts = list(str(n))
  3549. - for i in range((len(parts) - 3), 0, -3):
  3550. - parts.insert(i, ',')
  3551. - return ''.join(parts)
  3552. -
  3553. -r_bing = re.compile(r'<h3><a href="([^"]+)"')
  3554. -
  3555. -
  3556. -def bing_search(query, lang='en-GB'):
  3557. - base = 'http://www.bing.com/search?mkt=%s&q=' % lang
  3558. - bytes = web.get(base + query)
  3559. - m = r_bing.search(bytes)
  3560. - if m:
  3561. - return m.group(1)
  3562. -
  3563. -r_duck = re.compile(r'nofollow" class="[^"]+" href="(.*?)">')
  3564. -
  3565. -
  3566. -def duck_search(query):
  3567. - query = query.replace('!', '')
  3568. - uri = 'http://duckduckgo.com/html/?q=%s&kl=uk-en' % query
  3569. - bytes = web.get(uri)
  3570. - if 'web-result' in bytes: # filter out the adds on top of the page
  3571. - bytes = bytes.split('web-result')[1]
  3572. - m = r_duck.search(bytes)
  3573. - if m:
  3574. - return web.decode(m.group(1))
  3575. -
  3576. -# Alias google_search to duck_search
  3577. -google_search = duck_search
  3578. -
  3579. -
  3580. -def duck_api(query):
  3581. - if '!bang' in query.lower():
  3582. - return 'https://duckduckgo.com/bang.html'
  3583. -
  3584. - # This fixes issue #885 (https://github.com/sopel-irc/sopel/issues/885)
  3585. - # It seems that duckduckgo api redirects to its Instant answer API html page
  3586. - # if the query constains special charactares that aren't urlencoded.
  3587. - # So in order to always get a JSON response back the query is urlencoded
  3588. - query = quote_plus(query)
  3589. - uri = 'http://api.duckduckgo.com/?q=%s&format=json&no_html=1&no_redirect=1' % query
  3590. - results = json.loads(web.get(uri))
  3591. - if results['Redirect']:
  3592. - return results['Redirect']
  3593. - else:
  3594. - return None
  3595. -
  3596. -
  3597. -@commands('duck', 'ddg', 'g')
  3598. -@example('.duck privacy or .duck !mcwiki obsidian')
  3599. -def duck(bot, trigger):
  3600. - """Queries Duck Duck Go for the specified input."""
  3601. - query = trigger.group(2)
  3602. - if not query:
  3603. - return bot.reply('.ddg what?')
  3604. -
  3605. - # If the API gives us something, say it and stop
  3606. - result = duck_api(query)
  3607. - if result:
  3608. - bot.reply(result)
  3609. - return
  3610. -
  3611. - # Otherwise, look it up on the HTMl version
  3612. - uri = duck_search(query)
  3613. -
  3614. - if uri:
  3615. - bot.reply(uri)
  3616. - if 'last_seen_url' in bot.memory:
  3617. - bot.memory['last_seen_url'][trigger.sender] = uri
  3618. - else:
  3619. - bot.reply("No results found for '%s'." % query)
  3620. -
  3621. -
  3622. -@commands('search')
  3623. -@example('.search nerdfighter')
  3624. -def search(bot, trigger):
  3625. - """Searches Bing and Duck Duck Go."""
  3626. - if not trigger.group(2):
  3627. - return bot.reply('.search for what?')
  3628. - query = trigger.group(2)
  3629. - bu = bing_search(query) or '-'
  3630. - du = duck_search(query) or '-'
  3631. -
  3632. - if bu == du:
  3633. - result = '%s (b, d)' % bu
  3634. - else:
  3635. - if len(bu) > 150:
  3636. - bu = '(extremely long link)'
  3637. - if len(du) > 150:
  3638. - du = '(extremely long link)'
  3639. - result = '%s (b), %s (d)' % (bu, du)
  3640. -
  3641. - bot.reply(result)
  3642. -
  3643. -
  3644. -@commands('suggest')
  3645. -def suggest(bot, trigger):
  3646. - """Suggest terms starting with given input"""
  3647. - if not trigger.group(2):
  3648. - return bot.reply("No query term.")
  3649. - query = trigger.group(2)
  3650. - uri = 'http://websitedev.de/temp-bin/suggest.pl?q='
  3651. - answer = web.get(uri + query.replace('+', '%2B'))
  3652. - if answer:
  3653. - bot.say(answer)
  3654. - else:
  3655. - bot.reply('Sorry, no result.')
  3656. diff --git a/sopel/modules/seen.py b/sopel/modules/seen.py
  3657. deleted file mode 100644
  3658. index 262059c..0000000
  3659. --- a/sopel/modules/seen.py
  3660. +++ /dev/null
  3661. @@ -1,59 +0,0 @@
  3662. -# coding=utf-8
  3663. -"""
  3664. -seen.py - Sopel Seen Module
  3665. -Copyright 2008, Sean B. Palmer, inamidst.com
  3666. -Copyright © 2012, Elad Alfassa <[email protected]>
  3667. -Licensed under the Eiffel Forum License 2.
  3668. -
  3669. -http://sopel.chat
  3670. -"""
  3671. -from __future__ import unicode_literals, absolute_import, print_function, division
  3672. -
  3673. -import time
  3674. -import datetime
  3675. -from sopel.tools import Identifier
  3676. -from sopel.tools.time import get_timezone, format_time
  3677. -from sopel.module import commands, rule, priority, thread
  3678. -
  3679. -
  3680. -@commands('seen')
  3681. -def seen(bot, trigger):
  3682. - """Reports when and where the user was last seen."""
  3683. - if not trigger.group(2):
  3684. - bot.say(".seen <nick> - Reports when <nick> was last seen.")
  3685. - return
  3686. - nick = trigger.group(2).strip()
  3687. - timestamp = bot.db.get_nick_value(nick, 'seen_timestamp')
  3688. - if timestamp:
  3689. - channel = bot.db.get_nick_value(nick, 'seen_channel')
  3690. - message = bot.db.get_nick_value(nick, 'seen_message')
  3691. - action = bot.db.get_nick_value(nick, 'seen_action')
  3692. -
  3693. - tz = get_timezone(bot.db, bot.config, None, trigger.nick,
  3694. - trigger.sender)
  3695. - saw = datetime.datetime.utcfromtimestamp(timestamp)
  3696. - timestamp = format_time(bot.db, bot.config, tz, trigger.nick,
  3697. - trigger.sender, saw)
  3698. -
  3699. - msg = "I last saw {} at {}".format(nick, timestamp)
  3700. - if Identifier(channel) == trigger.sender:
  3701. - if action:
  3702. - msg = msg + " in here, doing " + nick + " " + message
  3703. - else:
  3704. - msg = msg + " in here, saying " + message
  3705. - else:
  3706. - msg += " in another channel."
  3707. - bot.say(str(trigger.nick) + ': ' + msg)
  3708. - else:
  3709. - bot.say("Sorry, I haven't seen {} around.".format(nick))
  3710. -
  3711. -
  3712. -@thread(False)
  3713. -@rule('(.*)')
  3714. -@priority('low')
  3715. -def note(bot, trigger):
  3716. - if not trigger.is_privmsg:
  3717. - bot.db.set_nick_value(trigger.nick, 'seen_timestamp', time.time())
  3718. - bot.db.set_nick_value(trigger.nick, 'seen_channel', trigger.sender)
  3719. - bot.db.set_nick_value(trigger.nick, 'seen_message', trigger)
  3720. - bot.db.set_nick_value(trigger.nick, 'seen_action', 'intent' in trigger.tags)
  3721. diff --git a/sopel/modules/spellcheck.py b/sopel/modules/spellcheck.py
  3722. deleted file mode 100644
  3723. index f722595..0000000
  3724. --- a/sopel/modules/spellcheck.py
  3725. +++ /dev/null
  3726. @@ -1,54 +0,0 @@
  3727. -# coding=utf-8
  3728. -"""
  3729. -spellcheck.py - Sopel spell check Module
  3730. -Copyright © 2012, Elad Alfassa, <[email protected]>
  3731. -Copyright © 2012, Lior Ramati
  3732. -Licensed under the Eiffel Forum License 2.
  3733. -
  3734. -http://sopel.chat
  3735. -
  3736. -This module relies on pyenchant, on Fedora and Red Hat based system, it can be found in the package python-enchant
  3737. -"""
  3738. -from __future__ import unicode_literals, absolute_import, print_function, division
  3739. -try:
  3740. - import enchant
  3741. -except ImportError:
  3742. - enchant = None
  3743. -from sopel.module import commands, example
  3744. -
  3745. -
  3746. -@commands('spellcheck', 'spell')
  3747. -@example('.spellcheck stuff')
  3748. -def spellcheck(bot, trigger):
  3749. - """
  3750. - Says whether the given word is spelled correctly, and gives suggestions if
  3751. - it's not.
  3752. - """
  3753. - if not enchant:
  3754. - bot.say("Missing pyenchant module.")
  3755. - if not trigger.group(2):
  3756. - return
  3757. - word = trigger.group(2).rstrip()
  3758. - if " " in word:
  3759. - bot.say("One word at a time, please")
  3760. - return
  3761. - dictionary = enchant.Dict("en_US")
  3762. - dictionary_uk = enchant.Dict("en_GB")
  3763. - # I don't want to make anyone angry, so I check both American and British English.
  3764. - if dictionary_uk.check(word):
  3765. - if dictionary.check(word):
  3766. - bot.say(word + " is spelled correctly")
  3767. - else:
  3768. - bot.say(word + " is spelled correctly (British)")
  3769. - elif dictionary.check(word):
  3770. - bot.say(word + " is spelled correctly (American)")
  3771. - else:
  3772. - msg = word + " is not spelled correctly. Maybe you want one of these spellings:"
  3773. - sugWords = []
  3774. - for suggested_word in dictionary.suggest(word):
  3775. - sugWords.append(suggested_word)
  3776. - for suggested_word in dictionary_uk.suggest(word):
  3777. - sugWords.append(suggested_word)
  3778. - for suggested_word in sorted(set(sugWords)): # removes duplicates
  3779. - msg = msg + " '" + suggested_word + "',"
  3780. - bot.say(msg)
  3781. diff --git a/sopel/modules/tell.py b/sopel/modules/tell.py
  3782. deleted file mode 100644
  3783. index ab7c397..0000000
  3784. --- a/sopel/modules/tell.py
  3785. +++ /dev/null
  3786. @@ -1,183 +0,0 @@
  3787. -# coding=utf-8
  3788. -"""
  3789. -tell.py - Sopel Tell and Ask Module
  3790. -Copyright 2008, Sean B. Palmer, inamidst.com
  3791. -Licensed under the Eiffel Forum License 2.
  3792. -
  3793. -http://sopel.chat
  3794. -"""
  3795. -from __future__ import unicode_literals, absolute_import, print_function, division
  3796. -
  3797. -import os
  3798. -import time
  3799. -import threading
  3800. -import sys
  3801. -from sopel.tools import Identifier, iterkeys
  3802. -from sopel.tools.time import get_timezone, format_time
  3803. -from sopel.module import commands, nickname_commands, rule, priority, example
  3804. -
  3805. -maximum = 4
  3806. -
  3807. -
  3808. -def loadReminders(fn, lock):
  3809. - lock.acquire()
  3810. - try:
  3811. - result = {}
  3812. - f = open(fn)
  3813. - for line in f:
  3814. - line = line.strip()
  3815. - if sys.version_info.major < 3:
  3816. - line = line.decode('utf-8')
  3817. - if line:
  3818. - try:
  3819. - tellee, teller, verb, timenow, msg = line.split('\t', 4)
  3820. - except ValueError:
  3821. - continue # @@ hmm
  3822. - result.setdefault(tellee, []).append((teller, verb, timenow, msg))
  3823. - f.close()
  3824. - finally:
  3825. - lock.release()
  3826. - return result
  3827. -
  3828. -
  3829. -def dumpReminders(fn, data, lock):
  3830. - lock.acquire()
  3831. - try:
  3832. - f = open(fn, 'w')
  3833. - for tellee in iterkeys(data):
  3834. - for remindon in data[tellee]:
  3835. - line = '\t'.join((tellee,) + remindon)
  3836. - try:
  3837. - to_write = line + '\n'
  3838. - if sys.version_info.major < 3:
  3839. - to_write = to_write.encode('utf-8')
  3840. - f.write(to_write)
  3841. - except IOError:
  3842. - break
  3843. - try:
  3844. - f.close()
  3845. - except IOError:
  3846. - pass
  3847. - finally:
  3848. - lock.release()
  3849. - return True
  3850. -
  3851. -
  3852. -def setup(self):
  3853. - fn = self.nick + '-' + self.config.core.host + '.tell.db'
  3854. - self.tell_filename = os.path.join(self.config.core.homedir, fn)
  3855. - if not os.path.exists(self.tell_filename):
  3856. - try:
  3857. - f = open(self.tell_filename, 'w')
  3858. - except OSError:
  3859. - pass
  3860. - else:
  3861. - f.write('')
  3862. - f.close()
  3863. - self.memory['tell_lock'] = threading.Lock()
  3864. - self.memory['reminders'] = loadReminders(self.tell_filename, self.memory['tell_lock'])
  3865. -
  3866. -
  3867. -@commands('tell', 'ask')
  3868. -@nickname_commands('tell', 'ask')
  3869. -@example('$nickname, tell Embolalia he broke something again.')
  3870. -def f_remind(bot, trigger):
  3871. - """Give someone a message the next time they're seen"""
  3872. - teller = trigger.nick
  3873. - verb = trigger.group(1)
  3874. -
  3875. - if not trigger.group(3):
  3876. - bot.reply("%s whom?" % verb)
  3877. - return
  3878. -
  3879. - tellee = trigger.group(3).rstrip('.,:;')
  3880. - msg = trigger.group(2).lstrip(tellee).lstrip()
  3881. -
  3882. - if not msg:
  3883. - bot.reply("%s %s what?" % (verb, tellee))
  3884. - return
  3885. -
  3886. - tellee = Identifier(tellee)
  3887. -
  3888. - if not os.path.exists(bot.tell_filename):
  3889. - return
  3890. -
  3891. - if len(tellee) > 20:
  3892. - return bot.reply('That nickname is too long.')
  3893. - if tellee == bot.nick:
  3894. - return bot.reply("I'm here now, you can tell me whatever you want!")
  3895. -
  3896. - if not tellee in (Identifier(teller), bot.nick, 'me'):
  3897. - tz = get_timezone(bot.db, bot.config, None, tellee)
  3898. - timenow = format_time(bot.db, bot.config, tz, tellee)
  3899. - bot.memory['tell_lock'].acquire()
  3900. - try:
  3901. - if not tellee in bot.memory['reminders']:
  3902. - bot.memory['reminders'][tellee] = [(teller, verb, timenow, msg)]
  3903. - else:
  3904. - bot.memory['reminders'][tellee].append((teller, verb, timenow, msg))
  3905. - finally:
  3906. - bot.memory['tell_lock'].release()
  3907. -
  3908. - response = "I'll pass that on when %s is around." % tellee
  3909. -
  3910. - bot.reply(response)
  3911. - elif Identifier(teller) == tellee:
  3912. - bot.say('You can %s yourself that.' % verb)
  3913. - else:
  3914. - bot.say("Hey, I'm not as stupid as Monty you know!")
  3915. -
  3916. - dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell
  3917. -
  3918. -
  3919. -def getReminders(bot, channel, key, tellee):
  3920. - lines = []
  3921. - template = "%s: %s <%s> %s %s %s"
  3922. - today = time.strftime('%d %b', time.gmtime())
  3923. -
  3924. - bot.memory['tell_lock'].acquire()
  3925. - try:
  3926. - for (teller, verb, datetime, msg) in bot.memory['reminders'][key]:
  3927. - if datetime.startswith(today):
  3928. - datetime = datetime[len(today) + 1:]
  3929. - lines.append(template % (tellee, datetime, teller, verb, tellee, msg))
  3930. -
  3931. - try:
  3932. - del bot.memory['reminders'][key]
  3933. - except KeyError:
  3934. - bot.msg(channel, 'Er...')
  3935. - finally:
  3936. - bot.memory['tell_lock'].release()
  3937. - return lines
  3938. -
  3939. -
  3940. -@rule('(.*)')
  3941. -@priority('low')
  3942. -def message(bot, trigger):
  3943. -
  3944. - tellee = trigger.nick
  3945. - channel = trigger.sender
  3946. -
  3947. - if not os.path.exists(bot.tell_filename):
  3948. - return
  3949. -
  3950. - reminders = []
  3951. - remkeys = list(reversed(sorted(bot.memory['reminders'].keys())))
  3952. -
  3953. - for remkey in remkeys:
  3954. - if not remkey.endswith('*') or remkey.endswith(':'):
  3955. - if tellee == remkey:
  3956. - reminders.extend(getReminders(bot, channel, remkey, tellee))
  3957. - elif tellee.startswith(remkey.rstrip('*:')):
  3958. - reminders.extend(getReminders(bot, channel, remkey, tellee))
  3959. -
  3960. - for line in reminders[:maximum]:
  3961. - bot.say(line)
  3962. -
  3963. - if reminders[maximum:]:
  3964. - bot.say('Further messages sent privately')
  3965. - for line in reminders[maximum:]:
  3966. - bot.msg(tellee, line)
  3967. -
  3968. - if len(bot.memory['reminders'].keys()) != remkeys:
  3969. - dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell
  3970. diff --git a/sopel/modules/tld.py b/sopel/modules/tld.py
  3971. deleted file mode 100644
  3972. index 62284b5..0000000
  3973. --- a/sopel/modules/tld.py
  3974. +++ /dev/null
  3975. @@ -1,69 +0,0 @@
  3976. -# coding=utf-8
  3977. -"""
  3978. -tld.py - Sopel TLD Module
  3979. -Copyright 2009-10, Michael Yanovich, yanovich.net
  3980. -Licensed under the Eiffel Forum License 2.
  3981. -
  3982. -http://sopel.chat
  3983. -"""
  3984. -from __future__ import unicode_literals, absolute_import, print_function, division
  3985. -
  3986. -from sopel import web
  3987. -from sopel.module import commands, example
  3988. -import re
  3989. -import sys
  3990. -if sys.version_info.major >= 3:
  3991. - unicode = str
  3992. -
  3993. -uri = 'https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains'
  3994. -r_tag = re.compile(r'<(?!!)[^>]+>')
  3995. -
  3996. -
  3997. -@commands('tld')
  3998. -@example('.tld ru')
  3999. -def gettld(bot, trigger):
  4000. - """Show information about the given Top Level Domain."""
  4001. - page = web.get(uri)
  4002. - tld = trigger.group(2)
  4003. - if tld[0] == '.':
  4004. - tld = tld[1:]
  4005. - search = r'(?i)<td><a href="\S+" title="\S+">\.{0}</a></td>\n(<td><a href=".*</a></td>\n)?<td>([A-Za-z0-9].*?)</td>\n<td>(.*)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
  4006. - search = search.format(tld)
  4007. - re_country = re.compile(search)
  4008. - matches = re_country.findall(page)
  4009. - if not matches:
  4010. - search = r'(?i)<td><a href="\S+" title="(\S+)">\.{0}</a></td>\n<td><a href=".*">(.*)</a></td>\n<td>([A-Za-z0-9].*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
  4011. - search = search.format(tld)
  4012. - re_country = re.compile(search)
  4013. - matches = re_country.findall(page)
  4014. - if matches:
  4015. - matches = list(matches[0])
  4016. - i = 0
  4017. - while i < len(matches):
  4018. - matches[i] = r_tag.sub("", matches[i])
  4019. - i += 1
  4020. - desc = matches[2]
  4021. - if len(desc) > 400:
  4022. - desc = desc[:400] + "..."
  4023. - reply = "%s -- %s. IDN: %s, DNSSEC: %s" % (matches[1], desc,
  4024. - matches[3], matches[4])
  4025. - bot.reply(reply)
  4026. - else:
  4027. - search = r'<td><a href="\S+" title="\S+">.{0}</a></td>\n<td><span class="flagicon"><img.*?\">(.*?)</a></td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
  4028. - search = search.format(unicode(tld))
  4029. - re_country = re.compile(search)
  4030. - matches = re_country.findall(page)
  4031. - if matches:
  4032. - matches = matches[0]
  4033. - dict_val = dict()
  4034. - dict_val["country"], dict_val["expl"], dict_val["notes"], dict_val["idn"], dict_val["dnssec"], dict_val["sld"] = matches
  4035. - for key in dict_val:
  4036. - if dict_val[key] == "&#160;":
  4037. - dict_val[key] = "N/A"
  4038. - dict_val[key] = r_tag.sub('', dict_val[key])
  4039. - if len(dict_val["notes"]) > 400:
  4040. - dict_val["notes"] = dict_val["notes"][:400] + "..."
  4041. - reply = "%s (%s, %s). IDN: %s, DNSSEC: %s, SLD: %s" % (dict_val["country"], dict_val["expl"], dict_val["notes"], dict_val["idn"], dict_val["dnssec"], dict_val["sld"])
  4042. - else:
  4043. - reply = "No matches found for TLD: {0}".format(unicode(tld))
  4044. - bot.reply(reply)
  4045. diff --git a/sopel/modules/translate.py b/sopel/modules/translate.py
  4046. deleted file mode 100644
  4047. index ef9281f..0000000
  4048. --- a/sopel/modules/translate.py
  4049. +++ /dev/null
  4050. @@ -1,203 +0,0 @@
  4051. -# coding=utf-8
  4052. -"""
  4053. -translate.py - Sopel Translation Module
  4054. -Copyright 2008, Sean B. Palmer, inamidst.com
  4055. -Copyright © 2013-2014, Elad Alfassa <[email protected]>
  4056. -Licensed under the Eiffel Forum License 2.
  4057. -
  4058. -http://sopel.chat
  4059. -"""
  4060. -from __future__ import unicode_literals, absolute_import, print_function, division
  4061. -from sopel import web
  4062. -from sopel.module import rule, commands, priority, example
  4063. -import json
  4064. -import sys
  4065. -import random
  4066. -import requests
  4067. -mangle_lines = {}
  4068. -if sys.version_info.major >= 3:
  4069. - unicode = str
  4070. -
  4071. -
  4072. -def translate(text, in_lang='auto', out_lang='en'):
  4073. - raw = False
  4074. - if unicode(out_lang).endswith('-raw'):
  4075. - out_lang = out_lang[:-4]
  4076. - raw = True
  4077. -
  4078. - headers = {
  4079. - 'User-Agent': 'Mozilla/5.0' +
  4080. - '(X11; U; Linux i686)' +
  4081. - 'Gecko/20071127 Firefox/2.0.0.11'
  4082. - }
  4083. -
  4084. - query = {
  4085. - "client": "gtx",
  4086. - "sl": in_lang,
  4087. - "tl": out_lang,
  4088. - "dt": "t",
  4089. - "q": text,
  4090. - }
  4091. - url = "http://translate.googleapis.com/translate_a/single"
  4092. - result = requests.get(url, params=query, timeout=40, headers=headers).text
  4093. -
  4094. - if result == '[,,""]':
  4095. - return None, in_lang
  4096. -
  4097. - while ',,' in result:
  4098. - result = result.replace(',,', ',null,')
  4099. - result = result.replace('[,', '[null,')
  4100. -
  4101. - data = json.loads(result)
  4102. -
  4103. - if raw:
  4104. - return str(data), 'en-raw'
  4105. -
  4106. - try:
  4107. - language = data[2] # -2][0][0]
  4108. - except:
  4109. - language = '?'
  4110. -
  4111. - return ''.join(x[0] for x in data[0]), language
  4112. -
  4113. -
  4114. -@rule(u'$nickname[,:]\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$')
  4115. -@example('$nickname: "mon chien"? or $nickname: fr "mon chien"?')
  4116. -@priority('low')
  4117. -def tr(bot, trigger):
  4118. - """Translates a phrase, with an optional language hint."""
  4119. - in_lang, out_lang, phrase = trigger.groups()
  4120. -
  4121. - if (len(phrase) > 350) and (not trigger.admin):
  4122. - return bot.reply('Phrase must be under 350 characters.')
  4123. -
  4124. - if phrase.strip() == '':
  4125. - return bot.reply('You need to specify a string for me to translate!')
  4126. -
  4127. - in_lang = in_lang or 'auto'
  4128. - out_lang = out_lang or 'en'
  4129. -
  4130. - if in_lang != out_lang:
  4131. - msg, in_lang = translate(phrase, in_lang, out_lang)
  4132. - if sys.version_info.major < 3 and isinstance(msg, str):
  4133. - msg = msg.decode('utf-8')
  4134. - if msg:
  4135. - msg = web.decode(msg) # msg.replace('&#39;', "'")
  4136. - msg = '"%s" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)
  4137. - else:
  4138. - msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (in_lang, out_lang)
  4139. -
  4140. - bot.reply(msg)
  4141. - else:
  4142. - bot.reply('Language guessing failed, so try suggesting one!')
  4143. -
  4144. -
  4145. -@commands('translate', 'tr')
  4146. -@example('.tr :en :fr my dog', '"mon chien" (en to fr, translate.google.com)')
  4147. -@example('.tr היי', '"Hey" (iw to en, translate.google.com)')
  4148. -@example('.tr mon chien', '"my dog" (fr to en, translate.google.com)')
  4149. -def tr2(bot, trigger):
  4150. - """Translates a phrase, with an optional language hint."""
  4151. - command = trigger.group(2)
  4152. -
  4153. - if not command:
  4154. - return bot.reply('You did not give me anything to translate')
  4155. -
  4156. - def langcode(p):
  4157. - return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
  4158. -
  4159. - args = ['auto', 'en']
  4160. -
  4161. - for i in range(2):
  4162. - if ' ' not in command:
  4163. - break
  4164. - prefix, cmd = command.split(' ', 1)
  4165. - if langcode(prefix):
  4166. - args[i] = prefix[1:]
  4167. - command = cmd
  4168. - phrase = command
  4169. -
  4170. - if (len(phrase) > 350) and (not trigger.admin):
  4171. - return bot.reply('Phrase must be under 350 characters.')
  4172. -
  4173. - if phrase.strip() == '':
  4174. - return bot.reply('You need to specify a string for me to translate!')
  4175. -
  4176. - src, dest = args
  4177. - if src != dest:
  4178. - msg, src = translate(phrase, src, dest)
  4179. - if sys.version_info.major < 3 and isinstance(msg, str):
  4180. - msg = msg.decode('utf-8')
  4181. - if msg:
  4182. - msg = web.decode(msg) # msg.replace('&#39;', "'")
  4183. - msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
  4184. - else:
  4185. - msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (src, dest)
  4186. -
  4187. - bot.reply(msg)
  4188. - else:
  4189. - bot.reply('Language guessing failed, so try suggesting one!')
  4190. -
  4191. -
  4192. -def get_random_lang(long_list, short_list):
  4193. - random_index = random.randint(0, len(long_list) - 1)
  4194. - random_lang = long_list[random_index]
  4195. - if random_lang not in short_list:
  4196. - short_list.append(random_lang)
  4197. - else:
  4198. - return get_random_lang(long_list, short_list)
  4199. - return short_list
  4200. -
  4201. -
  4202. -@commands('mangle', 'mangle2')
  4203. -def mangle(bot, trigger):
  4204. - """Repeatedly translate the input until it makes absolutely no sense."""
  4205. - global mangle_lines
  4206. - long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
  4207. - lang_list = []
  4208. - for __ in range(0, 8):
  4209. - lang_list = get_random_lang(long_lang_list, lang_list)
  4210. - random.shuffle(lang_list)
  4211. - if trigger.group(2) is None:
  4212. - try:
  4213. - phrase = (mangle_lines[trigger.sender.lower()], '')
  4214. - except:
  4215. - bot.reply("What do you want me to mangle?")
  4216. - return
  4217. - else:
  4218. - phrase = (trigger.group(2).strip(), '')
  4219. - if phrase[0] == '':
  4220. - bot.reply("What do you want me to mangle?")
  4221. - return
  4222. - for lang in lang_list:
  4223. - backup = phrase
  4224. - try:
  4225. - phrase = translate(phrase[0], 'en', lang)
  4226. - except:
  4227. - phrase = False
  4228. - if not phrase:
  4229. - phrase = backup
  4230. - break
  4231. -
  4232. - try:
  4233. - phrase = translate(phrase[0], lang, 'en')
  4234. - except:
  4235. - phrase = backup
  4236. - continue
  4237. -
  4238. - if not phrase:
  4239. - phrase = backup
  4240. - break
  4241. - bot.reply(phrase[0])
  4242. -
  4243. -
  4244. -@rule('(.*)')
  4245. -@priority('low')
  4246. -def collect_mangle_lines(bot, trigger):
  4247. - global mangle_lines
  4248. - mangle_lines[trigger.sender.lower()] = "%s said '%s'" % (trigger.nick, (trigger.group(0).strip()))
  4249. -
  4250. -
  4251. -if __name__ == "__main__":
  4252. - from sopel.test_tools import run_example_tests
  4253. - run_example_tests(__file__)
  4254. diff --git a/sopel/modules/unicode_info.py b/sopel/modules/unicode_info.py
  4255. deleted file mode 100644
  4256. index be5f44d..0000000
  4257. --- a/sopel/modules/unicode_info.py
  4258. +++ /dev/null
  4259. @@ -1,49 +0,0 @@
  4260. -# coding=utf-8
  4261. -"""Codepoints Module"""
  4262. -# Copyright 2013, Elsie Powell, embolalia.com
  4263. -# Copyright 2008, Sean B. Palmer, inamidst.com
  4264. -# Licensed under the Eiffel Forum License 2.
  4265. -from __future__ import unicode_literals, absolute_import, print_function, division
  4266. -import unicodedata
  4267. -import sys
  4268. -from sopel.module import commands, example, NOLIMIT
  4269. -
  4270. -if sys.version_info.major >= 3:
  4271. - unichr = chr
  4272. -
  4273. -
  4274. -@commands('u')
  4275. -@example('.u ‽', 'U+203D INTERROBANG (‽)')
  4276. -@example('.u 203D', 'U+203D INTERROBANG (‽)')
  4277. -def codepoint(bot, trigger):
  4278. - arg = trigger.group(2).strip()
  4279. - if len(arg) == 0:
  4280. - bot.reply('What code point do you want me to look up?')
  4281. - return NOLIMIT
  4282. - elif len(arg) > 1:
  4283. - if arg.startswith('U+'):
  4284. - arg = arg[2:]
  4285. - try:
  4286. - arg = unichr(int(arg, 16))
  4287. - except:
  4288. - bot.reply("That's not a valid code point.")
  4289. - return NOLIMIT
  4290. -
  4291. - # Get the hex value for the code point, and drop the 0x from the front
  4292. - point = str(hex(ord(u'' + arg)))[2:]
  4293. - # Make the hex 4 characters long with preceding 0s, and all upper case
  4294. - point = point.rjust(4, str('0')).upper()
  4295. - try:
  4296. - name = unicodedata.name(arg)
  4297. - except ValueError:
  4298. - return 'U+%s (No name found)' % point
  4299. -
  4300. - if not unicodedata.combining(arg):
  4301. - template = 'U+%s %s (%s)'
  4302. - else:
  4303. - template = 'U+%s %s (\xe2\x97\x8c%s)'
  4304. - bot.say(template % (point, name, arg))
  4305. -
  4306. -if __name__ == "__main__":
  4307. - from sopel.test_tools import run_example_tests
  4308. - run_example_tests(__file__)
  4309. diff --git a/sopel/modules/units.py b/sopel/modules/units.py
  4310. deleted file mode 100644
  4311. index 5355948..0000000
  4312. --- a/sopel/modules/units.py
  4313. +++ /dev/null
  4314. @@ -1,186 +0,0 @@
  4315. -# coding=utf-8
  4316. -"""
  4317. -units.py - Unit conversion module for Sopel
  4318. -Copyright © 2013, Elad Alfassa, <[email protected]>
  4319. -Copyright © 2013, Dimitri Molenaars, <[email protected]>
  4320. -Licensed under the Eiffel Forum License 2.
  4321. -
  4322. -"""
  4323. -from __future__ import unicode_literals, absolute_import, print_function, division
  4324. -from sopel.module import commands, example, NOLIMIT
  4325. -import re
  4326. -
  4327. -find_temp = re.compile('(-?[0-9]*\.?[0-9]*)[ °]*(K|C|F)', re.IGNORECASE)
  4328. -find_length = re.compile('([0-9]*\.?[0-9]*)[ ]*(mile[s]?|mi|inch|in|foot|feet|ft|yard[s]?|yd|(?:milli|centi|kilo|)meter[s]?|[mkc]?m|ly|light-year[s]?|au|astronomical unit[s]?|parsec[s]?|pc)', re.IGNORECASE)
  4329. -find_mass = re.compile('([0-9]*\.?[0-9]*)[ ]*(lb|lbm|pound[s]?|ounce|oz|(?:kilo|)gram(?:me|)[s]?|[k]?g)', re.IGNORECASE)
  4330. -
  4331. -
  4332. -def f_to_c(temp):
  4333. - return (float(temp) - 32) * 5 / 9
  4334. -
  4335. -
  4336. -def c_to_k(temp):
  4337. - return temp + 273.15
  4338. -
  4339. -
  4340. -def c_to_f(temp):
  4341. - return (9.0 / 5.0 * temp + 32)
  4342. -
  4343. -
  4344. -def k_to_c(temp):
  4345. - return temp - 273.15
  4346. -
  4347. -
  4348. -@commands('temp')
  4349. -@example('.temp 100F', '37.78°C = 100.00°F = 310.93K')
  4350. -@example('.temp 100C', '100.00°C = 212.00°F = 373.15K')
  4351. -@example('.temp 100K', '-173.15°C = -279.67°F = 100.00K')
  4352. -def temperature(bot, trigger):
  4353. - """
  4354. - Convert temperatures
  4355. - """
  4356. - try:
  4357. - source = find_temp.match(trigger.group(2)).groups()
  4358. - except (AttributeError, TypeError):
  4359. - bot.reply("That's not a valid temperature.")
  4360. - return NOLIMIT
  4361. - unit = source[1].upper()
  4362. - numeric = float(source[0])
  4363. - celsius = 0
  4364. - if unit == 'C':
  4365. - celsius = numeric
  4366. - elif unit == 'F':
  4367. - celsius = f_to_c(numeric)
  4368. - elif unit == 'K':
  4369. - celsius = k_to_c(numeric)
  4370. -
  4371. - kelvin = c_to_k(celsius)
  4372. - fahrenheit = c_to_f(celsius)
  4373. - bot.reply("{:.2f}°C = {:.2f}°F = {:.2f}K".format(celsius, fahrenheit, kelvin))
  4374. -
  4375. -
  4376. -@commands('length', 'distance')
  4377. -@example('.distance 3m', '3.00m = 9 feet, 10.11 inches')
  4378. -@example('.distance 3km', '3.00km = 1.86 miles')
  4379. -@example('.distance 3 miles', '4.83km = 3.00 miles')
  4380. -@example('.distance 3 inch', '7.62cm = 3.00 inches')
  4381. -@example('.distance 3 feet', '91.44cm = 3 feet, 0.00 inches')
  4382. -@example('.distance 3 yards', '2.74m = 9 feet, 0.00 inches')
  4383. -@example('.distance 155cm', '1.55m = 5 feet, 1.02 inches')
  4384. -@example('.length 3 ly', '28382191417742.40km = 17635876112814.77 miles')
  4385. -@example('.length 3 au', '448793612.10km = 278867421.71 miles')
  4386. -@example('.length 3 parsec', '92570329129020.20km = 57520535754731.61 miles')
  4387. -def distance(bot, trigger):
  4388. - """
  4389. - Convert distances
  4390. - """
  4391. - try:
  4392. - source = find_length.match(trigger.group(2)).groups()
  4393. - except (AttributeError, TypeError):
  4394. - bot.reply("That's not a valid length unit.")
  4395. - return NOLIMIT
  4396. - unit = source[1].lower()
  4397. - numeric = float(source[0])
  4398. - meter = 0
  4399. - if unit in ("meters", "meter", "m"):
  4400. - meter = numeric
  4401. - elif unit in ("millimeters", "millimeter", "mm"):
  4402. - meter = numeric / 1000
  4403. - elif unit in ("kilometers", "kilometer", "km"):
  4404. - meter = numeric * 1000
  4405. - elif unit in ("miles", "mile", "mi"):
  4406. - meter = numeric / 0.00062137
  4407. - elif unit in ("inch", "in"):
  4408. - meter = numeric / 39.370
  4409. - elif unit in ("centimeters", "centimeter", "cm"):
  4410. - meter = numeric / 100
  4411. - elif unit in ("feet", "foot", "ft"):
  4412. - meter = numeric / 3.2808
  4413. - elif unit in ("yards", "yard", "yd"):
  4414. - meter = numeric / (3.2808 / 3)
  4415. - elif unit in ("light-year", "light-years", "ly"):
  4416. - meter = numeric * 9460730472580800
  4417. - elif unit in ("astronomical unit", "astronomical units", "au"):
  4418. - meter = numeric * 149597870700
  4419. - elif unit in ("parsec", "parsecs", "pc"):
  4420. - meter = numeric * 30856776376340068
  4421. -
  4422. - if meter >= 1000:
  4423. - metric_part = '{:.2f}km'.format(meter / 1000)
  4424. - elif meter < 0.01:
  4425. - metric_part = '{:.2f}mm'.format(meter * 1000)
  4426. - elif meter < 1:
  4427. - metric_part = '{:.2f}cm'.format(meter * 100)
  4428. - else:
  4429. - metric_part = '{:.2f}m'.format(meter)
  4430. -
  4431. - # Shit like this makes me hate being an American.
  4432. - inch = meter * 39.37
  4433. - foot = int(inch) // 12
  4434. - inch = inch - (foot * 12)
  4435. - yard = foot // 3
  4436. - mile = meter * 0.000621371192
  4437. -
  4438. - if yard > 500:
  4439. - stupid_part = '{:.2f} miles'.format(mile)
  4440. - else:
  4441. - parts = []
  4442. - if yard >= 100:
  4443. - parts.append('{} yards'.format(yard))
  4444. - foot -= (yard * 3)
  4445. -
  4446. - if foot == 1:
  4447. - parts.append('1 foot')
  4448. - elif foot != 0:
  4449. - parts.append('{:.0f} feet'.format(foot))
  4450. -
  4451. - parts.append('{:.2f} inches'.format(inch))
  4452. -
  4453. - stupid_part = ', '.join(parts)
  4454. -
  4455. - bot.reply('{} = {}'.format(metric_part, stupid_part))
  4456. -
  4457. -
  4458. -@commands('weight', 'mass')
  4459. -def mass(bot, trigger):
  4460. - """
  4461. - Convert mass
  4462. - """
  4463. - try:
  4464. - source = find_mass.match(trigger.group(2)).groups()
  4465. - except (AttributeError, TypeError):
  4466. - bot.reply("That's not a valid mass unit.")
  4467. - return NOLIMIT
  4468. - unit = source[1].lower()
  4469. - numeric = float(source[0])
  4470. - metric = 0
  4471. - if unit in ("gram", "grams", "gramme", "grammes", "g"):
  4472. - metric = numeric
  4473. - elif unit in ("kilogram", "kilograms", "kilogramme", "kilogrammes", "kg"):
  4474. - metric = numeric * 1000
  4475. - elif unit in ("lb", "lbm", "pound", "pounds"):
  4476. - metric = numeric * 453.59237
  4477. - elif unit in ("oz", "ounce"):
  4478. - metric = numeric * 28.35
  4479. -
  4480. - if metric >= 1000:
  4481. - metric_part = '{:.2f}kg'.format(metric / 1000)
  4482. - else:
  4483. - metric_part = '{:.2f}g'.format(metric)
  4484. -
  4485. - ounce = metric * .035274
  4486. - pound = int(ounce) // 16
  4487. - ounce = ounce - (pound * 16)
  4488. -
  4489. - if pound > 1:
  4490. - stupid_part = '{} pounds'.format(pound)
  4491. - if ounce > 0.01:
  4492. - stupid_part += ' {:.2f} ounces'.format(ounce)
  4493. - else:
  4494. - stupid_part = '{:.2f} oz'.format(ounce)
  4495. -
  4496. - bot.reply('{} = {}'.format(metric_part, stupid_part))
  4497. -
  4498. -if __name__ == "__main__":
  4499. - from sopel.test_tools import run_example_tests
  4500. - run_example_tests(__file__)
  4501. diff --git a/sopel/modules/uptime.py b/sopel/modules/uptime.py
  4502. deleted file mode 100644
  4503. index 7e3fe1d..0000000
  4504. --- a/sopel/modules/uptime.py
  4505. +++ /dev/null
  4506. @@ -1,27 +0,0 @@
  4507. -# coding=utf-8
  4508. -"""
  4509. -uptime.py - Uptime module
  4510. -Copyright 2014, Fabian Neundorf
  4511. -Licensed under the Eiffel Forum License 2.
  4512. -
  4513. -http://sopel.chat
  4514. -"""
  4515. -from __future__ import unicode_literals, absolute_import, print_function, division
  4516. -
  4517. -from sopel.module import commands
  4518. -import datetime
  4519. -
  4520. -
  4521. -def setup(bot):
  4522. - if "uptime" not in bot.memory:
  4523. - bot.memory["uptime"] = datetime.datetime.utcnow()
  4524. -
  4525. -
  4526. -@commands('uptime')
  4527. -def uptime(bot, trigger):
  4528. - """.uptime - Returns the uptime of Sopel."""
  4529. - delta = datetime.timedelta(seconds=round((datetime.datetime.utcnow() -
  4530. - bot.memory["uptime"])
  4531. - .total_seconds()))
  4532. - bot.say("I've been sitting here for {} and I keep "
  4533. - "going!".format(delta))
  4534. diff --git a/sopel/modules/url.py b/sopel/modules/url.py
  4535. deleted file mode 100644
  4536. index d2a83df..0000000
  4537. --- a/sopel/modules/url.py
  4538. +++ /dev/null
  4539. @@ -1,237 +0,0 @@
  4540. -# coding=utf-8
  4541. -"""URL title module"""
  4542. -# Copyright 2010-2011, Michael Yanovich, yanovich.net, Kenneth Sham
  4543. -# Copyright 2012-2013 Elsie Powell
  4544. -# Copyright 2013 Lior Ramati ([email protected])
  4545. -# Copyright © 2014 Elad Alfassa <[email protected]>
  4546. -# Licensed under the Eiffel Forum License 2.
  4547. -from __future__ import unicode_literals, absolute_import, print_function, division
  4548. -
  4549. -import re
  4550. -from contextlib import closing
  4551. -from sopel import web, tools
  4552. -from sopel.module import commands, rule, example
  4553. -from sopel.config.types import ValidatedAttribute, ListAttribute, StaticSection
  4554. -
  4555. -import requests
  4556. -
  4557. -url_finder = None
  4558. -# These are used to clean up the title tag before actually parsing it. Not the
  4559. -# world's best way to do this, but it'll do for now.
  4560. -title_tag_data = re.compile('<(/?)title( [^>]+)?>', re.IGNORECASE)
  4561. -quoted_title = re.compile('[\'"]<title>[\'"]', re.IGNORECASE)
  4562. -# This is another regex that presumably does something important.
  4563. -re_dcc = re.compile(r'(?i)dcc\ssend')
  4564. -# This sets the maximum number of bytes that should be read in order to find
  4565. -# the title. We don't want it too high, or a link to a big file/stream will
  4566. -# just keep downloading until there's no more memory. 640k ought to be enough
  4567. -# for anybody.
  4568. -max_bytes = 655360
  4569. -
  4570. -
  4571. -class UrlSection(StaticSection):
  4572. - # TODO some validation rules maybe?
  4573. - exclude = ListAttribute('exclude')
  4574. - exclusion_char = ValidatedAttribute('exclusion_char', default='!')
  4575. -
  4576. -
  4577. -def configure(config):
  4578. - config.define_section('url', UrlSection)
  4579. - config.url.configure_setting(
  4580. - 'exclude',
  4581. - 'Enter regular expressions for each URL you would like to exclude.'
  4582. - )
  4583. - config.url.configure_setting(
  4584. - 'exclusion_char',
  4585. - 'Enter a character which can be prefixed to suppress URL titling'
  4586. - )
  4587. -
  4588. -
  4589. -def setup(bot=None):
  4590. - global url_finder
  4591. -
  4592. - # TODO figure out why this is needed, and get rid of it, because really?
  4593. - if not bot:
  4594. - return
  4595. - bot.config.define_section('url', UrlSection)
  4596. -
  4597. - if bot.config.url.exclude:
  4598. - regexes = [re.compile(s) for s in bot.config.url.exclude]
  4599. - else:
  4600. - regexes = []
  4601. -
  4602. - # We're keeping these in their own list, rather than putting then in the
  4603. - # callbacks list because 1, it's easier to deal with modules that are still
  4604. - # using this list, and not the newer callbacks list and 2, having a lambda
  4605. - # just to pass is kinda ugly.
  4606. - if not bot.memory.contains('url_exclude'):
  4607. - bot.memory['url_exclude'] = regexes
  4608. - else:
  4609. - exclude = bot.memory['url_exclude']
  4610. - if regexes:
  4611. - exclude.extend(regexes)
  4612. - bot.memory['url_exclude'] = exclude
  4613. -
  4614. - # Ensure that url_callbacks and last_seen_url are in memory
  4615. - if not bot.memory.contains('url_callbacks'):
  4616. - bot.memory['url_callbacks'] = tools.SopelMemory()
  4617. - if not bot.memory.contains('last_seen_url'):
  4618. - bot.memory['last_seen_url'] = tools.SopelMemory()
  4619. -
  4620. - url_finder = re.compile(r'(?u)(%s?(?:http|https|ftp)(?:://\S+))' %
  4621. - (bot.config.url.exclusion_char), re.IGNORECASE)
  4622. -
  4623. -
  4624. -@commands('title')
  4625. -@example('.title http://google.com', '[ Google ] - google.com')
  4626. -def title_command(bot, trigger):
  4627. - """
  4628. - Show the title or URL information for the given URL, or the last URL seen
  4629. - in this channel.
  4630. - """
  4631. - if not trigger.group(2):
  4632. - if trigger.sender not in bot.memory['last_seen_url']:
  4633. - return
  4634. - matched = check_callbacks(bot, trigger,
  4635. - bot.memory['last_seen_url'][trigger.sender],
  4636. - True)
  4637. - if matched:
  4638. - return
  4639. - else:
  4640. - urls = [bot.memory['last_seen_url'][trigger.sender]]
  4641. - else:
  4642. - urls = re.findall(url_finder, trigger)
  4643. -
  4644. - results = process_urls(bot, trigger, urls)
  4645. - for title, domain in results[:4]:
  4646. - bot.reply('[ %s ] - %s' % (title, domain))
  4647. -
  4648. -
  4649. -@rule('(?u).*(https?://\S+).*')
  4650. -def title_auto(bot, trigger):
  4651. - """
  4652. - Automatically show titles for URLs. For shortened URLs/redirects, find
  4653. - where the URL redirects to and show the title for that (or call a function
  4654. - from another module to give more information).
  4655. - """
  4656. - if re.match(bot.config.core.prefix + 'title', trigger):
  4657. - return
  4658. -
  4659. - # Avoid fetching known malicious links
  4660. - if 'safety_cache' in bot.memory and trigger in bot.memory['safety_cache']:
  4661. - if bot.memory['safety_cache'][trigger]['positives'] > 1:
  4662. - return
  4663. -
  4664. - urls = re.findall(url_finder, trigger)
  4665. - if len(urls) == 0:
  4666. - return
  4667. -
  4668. - results = process_urls(bot, trigger, urls)
  4669. - bot.memory['last_seen_url'][trigger.sender] = urls[-1]
  4670. -
  4671. - for title, domain in results[:4]:
  4672. - message = '[ %s ] - %s' % (title, domain)
  4673. - # Guard against responding to other instances of this bot.
  4674. - if message != trigger:
  4675. - bot.say(message)
  4676. -
  4677. -
  4678. -def process_urls(bot, trigger, urls):
  4679. - """
  4680. - For each URL in the list, ensure that it isn't handled by another module.
  4681. - If not, find where it redirects to, if anywhere. If that redirected URL
  4682. - should be handled by another module, dispatch the callback for it.
  4683. - Return a list of (title, hostname) tuples for each URL which is not handled by
  4684. - another module.
  4685. - """
  4686. -
  4687. - results = []
  4688. - for url in urls:
  4689. - if not url.startswith(bot.config.url.exclusion_char):
  4690. - # Magic stuff to account for international domain names
  4691. - try:
  4692. - url = web.iri_to_uri(url)
  4693. - except:
  4694. - pass
  4695. - # First, check that the URL we got doesn't match
  4696. - matched = check_callbacks(bot, trigger, url, False)
  4697. - if matched:
  4698. - continue
  4699. - # Finally, actually show the URL
  4700. - title = find_title(url)
  4701. - if title:
  4702. - results.append((title, get_hostname(url)))
  4703. - return results
  4704. -
  4705. -
  4706. -def check_callbacks(bot, trigger, url, run=True):
  4707. - """
  4708. - Check the given URL against the callbacks list. If it matches, and ``run``
  4709. - is given as ``True``, run the callback function, otherwise pass. Returns
  4710. - ``True`` if the url matched anything in the callbacks list.
  4711. - """
  4712. - # Check if it matches the exclusion list first
  4713. - matched = any(regex.search(url) for regex in bot.memory['url_exclude'])
  4714. - # Then, check if there's anything in the callback list
  4715. - for regex, function in tools.iteritems(bot.memory['url_callbacks']):
  4716. - match = regex.search(url)
  4717. - if match:
  4718. - if run:
  4719. - function(bot, trigger, match)
  4720. - matched = True
  4721. - return matched
  4722. -
  4723. -
  4724. -def find_title(url):
  4725. - """Return the title for the given URL."""
  4726. - response = requests.get(url, stream=True)
  4727. - try:
  4728. - content = ''
  4729. - for byte in response.iter_content(chunk_size=512, decode_unicode=True):
  4730. - if not isinstance(byte, bytes):
  4731. - content += byte
  4732. - else:
  4733. - break
  4734. - if '</title>' in content or len(content) > max_bytes:
  4735. - break
  4736. - except UnicodeDecodeError:
  4737. - return # Fail silently when data can't be decoded
  4738. - finally:
  4739. - # need to close the connexion because we have not read all the data
  4740. - response.close()
  4741. -
  4742. - # Some cleanup that I don't really grok, but was in the original, so
  4743. - # we'll keep it (with the compiled regexes made global) for now.
  4744. - content = title_tag_data.sub(r'<\1title>', content)
  4745. - content = quoted_title.sub('', content)
  4746. -
  4747. - start = content.find('<title>')
  4748. - end = content.find('</title>')
  4749. - if start == -1 or end == -1:
  4750. - return
  4751. - title = web.decode(content[start + 7:end])
  4752. - title = title.strip()[:200]
  4753. -
  4754. - title = ' '.join(title.split()) # cleanly remove multiple spaces
  4755. -
  4756. - # More cryptic regex substitutions. This one looks to be myano's invention.
  4757. - title = re_dcc.sub('', title)
  4758. -
  4759. - return title or None
  4760. -
  4761. -
  4762. -def get_hostname(url):
  4763. - idx = 7
  4764. - if url.startswith('https://'):
  4765. - idx = 8
  4766. - elif url.startswith('ftp://'):
  4767. - idx = 6
  4768. - hostname = url[idx:]
  4769. - slash = hostname.find('/')
  4770. - if slash != -1:
  4771. - hostname = hostname[:slash]
  4772. - return hostname
  4773. -
  4774. -if __name__ == "__main__":
  4775. - from sopel.test_tools import run_example_tests
  4776. - run_example_tests(__file__)
  4777. diff --git a/sopel/modules/version.py b/sopel/modules/version.py
  4778. deleted file mode 100644
  4779. index a1b4219..0000000
  4780. --- a/sopel/modules/version.py
  4781. +++ /dev/null
  4782. @@ -1,81 +0,0 @@
  4783. -# coding=utf-8
  4784. -"""
  4785. -version.py - Sopel Version Module
  4786. -Copyright 2009, Silas Baronda
  4787. -Copyright 2014, Dimitri Molenaars <[email protected]>
  4788. -Licensed under the Eiffel Forum License 2.
  4789. -
  4790. -http://sopel.chat
  4791. -"""
  4792. -from __future__ import unicode_literals, absolute_import, print_function, division
  4793. -
  4794. -from datetime import datetime
  4795. -import sopel
  4796. -import re
  4797. -from os import path
  4798. -
  4799. -log_line = re.compile('\S+ (\S+) (.*? <.*?>) (\d+) (\S+)\tcommit[^:]*: (.+)')
  4800. -
  4801. -
  4802. -def git_info():
  4803. - repo = path.join(path.dirname(path.dirname(path.dirname(__file__))), '.git')
  4804. - head = path.join(repo, 'HEAD')
  4805. - if path.isfile(head):
  4806. - with open(head) as h:
  4807. - head_loc = h.readline()[5:-1] # strip ref: and \n
  4808. - head_file = path.join(repo, head_loc)
  4809. - if path.isfile(head_file):
  4810. - with open(head_file) as h:
  4811. - sha = h.readline()
  4812. - if sha:
  4813. - return sha
  4814. -
  4815. -
  4816. [email protected]('version')
  4817. -def version(bot, trigger):
  4818. - """Display the latest commit version, if Sopel is running in a git repo."""
  4819. - release = sopel.__version__
  4820. - sha = git_info()
  4821. - if not sha:
  4822. - msg = 'Sopel v. ' + release
  4823. - if release[-4:] == '-git':
  4824. - msg += ' at unknown commit.'
  4825. - bot.reply(msg)
  4826. - return
  4827. -
  4828. - bot.reply("Sopel v. {} at commit: {}".format(sopel.__version__, sha))
  4829. -
  4830. -
  4831. [email protected]('VERSION')
  4832. -def ctcp_version(bot, trigger):
  4833. - print('wat')
  4834. - bot.write(('NOTICE', trigger.nick),
  4835. - '\x01VERSION Sopel IRC Bot version %s\x01' % sopel.__version__)
  4836. -
  4837. -
  4838. [email protected]('\x01SOURCE\x01')
  4839. -def ctcp_source(bot, trigger):
  4840. - bot.write(('NOTICE', trigger.nick),
  4841. - '\x01SOURCE https://github.com/sopel-irc/sopel/\x01')
  4842. -
  4843. -
  4844. [email protected]('\x01PING\s(.*)\x01')
  4845. -def ctcp_ping(bot, trigger):
  4846. - text = trigger.group()
  4847. - text = text.replace("PING ", "")
  4848. - text = text.replace("\x01", "")
  4849. - bot.write(('NOTICE', trigger.nick),
  4850. - '\x01PING {0}\x01'.format(text))
  4851. -
  4852. -
  4853. [email protected]('\x01TIME\x01')
  4854. -def ctcp_time(bot, trigger):
  4855. - dt = datetime.now()
  4856. - current_time = dt.strftime("%A, %d. %B %Y %I:%M%p")
  4857. - bot.write(('NOTICE', trigger.nick),
  4858. - '\x01TIME {0}\x01'.format(current_time))
  4859. diff --git a/sopel/modules/weather.py b/sopel/modules/weather.py
  4860. deleted file mode 100644
  4861. index ea03b46..0000000
  4862. --- a/sopel/modules/weather.py
  4863. +++ /dev/null
  4864. @@ -1,183 +0,0 @@
  4865. -# coding=utf-8
  4866. -# Copyright 2008, Sean B. Palmer, inamidst.com
  4867. -# Copyright 2012, Elsie Powell, embolalia.com
  4868. -# Licensed under the Eiffel Forum License 2.
  4869. -from __future__ import unicode_literals, absolute_import, print_function, division
  4870. -
  4871. -from sopel import web
  4872. -from sopel.module import commands, example, NOLIMIT
  4873. -
  4874. -import xmltodict
  4875. -
  4876. -
  4877. -def woeid_search(query):
  4878. - """
  4879. - Find the first Where On Earth ID for the given query. Result is the etree
  4880. - node for the result, so that location data can still be retrieved. Returns
  4881. - None if there is no result, or the woeid field is empty.
  4882. - """
  4883. - query = 'q=select * from geo.places where text="%s"' % query
  4884. - body = web.get('http://query.yahooapis.com/v1/public/yql?' + query,
  4885. - dont_decode=True)
  4886. - parsed = xmltodict.parse(body).get('query')
  4887. - results = parsed.get('results')
  4888. - if results is None or results.get('place') is None:
  4889. - return None
  4890. - if type(results.get('place')) is list:
  4891. - return results.get('place')[0]
  4892. - return results.get('place')
  4893. -
  4894. -
  4895. -def get_cover(parsed):
  4896. - try:
  4897. - condition = parsed['channel']['item']['yweather:condition']
  4898. - except KeyError:
  4899. - return 'unknown'
  4900. - text = condition['@text']
  4901. - # code = int(condition['code'])
  4902. - # TODO parse code to get those little icon thingies.
  4903. - return text
  4904. -
  4905. -
  4906. -def get_temp(parsed):
  4907. - try:
  4908. - condition = parsed['channel']['item']['yweather:condition']
  4909. - temp = int(condition['@temp'])
  4910. - except (KeyError, ValueError):
  4911. - return 'unknown'
  4912. - f = round((temp * 1.8) + 32, 2)
  4913. - return (u'%d\u00B0C (%d\u00B0F)' % (temp, f))
  4914. -
  4915. -
  4916. -def get_humidity(parsed):
  4917. - try:
  4918. - humidity = parsed['channel']['yweather:atmosphere']['@humidity']
  4919. - except (KeyError, ValueError):
  4920. - return 'unknown'
  4921. - return "Humidity: %s%%" % humidity
  4922. -
  4923. -
  4924. -def get_wind(parsed):
  4925. - try:
  4926. - wind_data = parsed['channel']['yweather:wind']
  4927. - kph = float(wind_data['@speed'])
  4928. - m_s = float(round(kph / 3.6, 1))
  4929. - speed = int(round(kph / 1.852, 0))
  4930. - degrees = int(wind_data['@direction'])
  4931. - except (KeyError, ValueError):
  4932. - return 'unknown'
  4933. -
  4934. - if speed < 1:
  4935. - description = 'Calm'
  4936. - elif speed < 4:
  4937. - description = 'Light air'
  4938. - elif speed < 7:
  4939. - description = 'Light breeze'
  4940. - elif speed < 11:
  4941. - description = 'Gentle breeze'
  4942. - elif speed < 16:
  4943. - description = 'Moderate breeze'
  4944. - elif speed < 22:
  4945. - description = 'Fresh breeze'
  4946. - elif speed < 28:
  4947. - description = 'Strong breeze'
  4948. - elif speed < 34:
  4949. - description = 'Near gale'
  4950. - elif speed < 41:
  4951. - description = 'Gale'
  4952. - elif speed < 48:
  4953. - description = 'Strong gale'
  4954. - elif speed < 56:
  4955. - description = 'Storm'
  4956. - elif speed < 64:
  4957. - description = 'Violent storm'
  4958. - else:
  4959. - description = 'Hurricane'
  4960. -
  4961. - if (degrees <= 22.5) or (degrees > 337.5):
  4962. - degrees = u'\u2193'
  4963. - elif (degrees > 22.5) and (degrees <= 67.5):
  4964. - degrees = u'\u2199'
  4965. - elif (degrees > 67.5) and (degrees <= 112.5):
  4966. - degrees = u'\u2190'
  4967. - elif (degrees > 112.5) and (degrees <= 157.5):
  4968. - degrees = u'\u2196'
  4969. - elif (degrees > 157.5) and (degrees <= 202.5):
  4970. - degrees = u'\u2191'
  4971. - elif (degrees > 202.5) and (degrees <= 247.5):
  4972. - degrees = u'\u2197'
  4973. - elif (degrees > 247.5) and (degrees <= 292.5):
  4974. - degrees = u'\u2192'
  4975. - elif (degrees > 292.5) and (degrees <= 337.5):
  4976. - degrees = u'\u2198'
  4977. -
  4978. - return description + ' ' + str(m_s) + 'm/s (' + degrees + ')'
  4979. -
  4980. -
  4981. -@commands('weather', 'wea')
  4982. -@example('.weather London')
  4983. -def weather(bot, trigger):
  4984. - """.weather location - Show the weather at the given location."""
  4985. -
  4986. - location = trigger.group(2)
  4987. - woeid = ''
  4988. - if not location:
  4989. - woeid = bot.db.get_nick_value(trigger.nick, 'woeid')
  4990. - if not woeid:
  4991. - return bot.msg(trigger.sender, "I don't know where you live. " +
  4992. - 'Give me a location, like .weather London, or tell me where you live by saying .setlocation London, for example.')
  4993. - else:
  4994. - location = location.strip()
  4995. - woeid = bot.db.get_nick_value(location, 'woeid')
  4996. - if woeid is None:
  4997. - first_result = woeid_search(location)
  4998. - if first_result is not None:
  4999. - woeid = first_result.get('woeid')
  5000. -
  5001. - if not woeid:
  5002. - return bot.reply("I don't know where that is.")
  5003. -
  5004. - query = web.urlencode({'w': woeid, 'u': 'c'})
  5005. - raw = web.get('http://weather.yahooapis.com/forecastrss?' + query,
  5006. - dont_decode=True)
  5007. - parsed = xmltodict.parse(raw).get('rss')
  5008. - location = parsed.get('channel').get('title')
  5009. -
  5010. - cover = get_cover(parsed)
  5011. - temp = get_temp(parsed)
  5012. - humidity = get_humidity(parsed)
  5013. - wind = get_wind(parsed)
  5014. - bot.say(u'%s: %s, %s, %s, %s' % (location, cover, temp, humidity, wind))
  5015. -
  5016. -
  5017. -@commands('setlocation', 'setwoeid')
  5018. -@example('.setlocation Columbus, OH')
  5019. -def update_woeid(bot, trigger):
  5020. - """Set your default weather location."""
  5021. - if not trigger.group(2):
  5022. - bot.reply('Give me a location, like "Washington, DC" or "London".')
  5023. - return NOLIMIT
  5024. -
  5025. - first_result = woeid_search(trigger.group(2))
  5026. - if first_result is None:
  5027. - return bot.reply("I don't know where that is.")
  5028. -
  5029. - woeid = first_result.get('woeid')
  5030. -
  5031. - bot.db.set_nick_value(trigger.nick, 'woeid', woeid)
  5032. -
  5033. - neighborhood = first_result.get('locality2') or ''
  5034. - if neighborhood:
  5035. - neighborhood = neighborhood.get('#text') + ', '
  5036. - city = first_result.get('locality1') or ''
  5037. - # This is to catch cases like 'Bawlf, Alberta' where the location is
  5038. - # thought to be a "LocalAdmin" rather than a "Town"
  5039. - if city:
  5040. - city = city.get('#text')
  5041. - else:
  5042. - city = first_result.get('name')
  5043. - state = first_result.get('admin1').get('#text') or ''
  5044. - country = first_result.get('country').get('#text') or ''
  5045. - uzip = first_result.get('postal').get('#text') or ''
  5046. - bot.reply('I now have you at WOEID %s (%s%s, %s, %s %s)' %
  5047. - (woeid, neighborhood, city, state, country, uzip))
  5048. diff --git a/sopel/modules/wikipedia.py b/sopel/modules/wikipedia.py
  5049. deleted file mode 100644
  5050. index 69dffb9..0000000
  5051. --- a/sopel/modules/wikipedia.py
  5052. +++ /dev/null
  5053. @@ -1,133 +0,0 @@
  5054. -# coding=utf-8
  5055. -# Copyright 2013 Elsie Powell - embolalia.com
  5056. -# Licensed under the Eiffel Forum License 2.
  5057. -from __future__ import unicode_literals, absolute_import, print_function, division
  5058. -from sopel import web, tools
  5059. -from sopel.config.types import StaticSection, ValidatedAttribute
  5060. -from sopel.module import NOLIMIT, commands, example, rule
  5061. -import json
  5062. -import re
  5063. -
  5064. -import sys
  5065. -if sys.version_info.major < 3:
  5066. - from urlparse import unquote as _unquote
  5067. - unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
  5068. -else:
  5069. - from urllib.parse import unquote
  5070. -
  5071. -REDIRECT = re.compile(r'^REDIRECT (.*)')
  5072. -
  5073. -
  5074. -class WikipediaSection(StaticSection):
  5075. - default_lang = ValidatedAttribute('default_lang', default='en')
  5076. - """The default language to find articles from."""
  5077. - lang_per_channel = ValidatedAttribute('lang_per_channel')
  5078. -
  5079. -
  5080. -def setup(bot):
  5081. - bot.config.define_section('wikipedia', WikipediaSection)
  5082. -
  5083. - regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')
  5084. - if not bot.memory.contains('url_callbacks'):
  5085. - bot.memory['url_callbacks'] = tools.SopelMemory()
  5086. - bot.memory['url_callbacks'][regex] = mw_info
  5087. -
  5088. -
  5089. -def configure(config):
  5090. - config.define_section('wikipedia', WikipediaSection)
  5091. - config.wikipedia.configure_setting(
  5092. - 'default_lang',
  5093. - "Enter the default language to find articles from."
  5094. - )
  5095. -
  5096. -
  5097. -def mw_search(server, query, num):
  5098. - """
  5099. - Searches the specified MediaWiki server for the given query, and returns
  5100. - the specified number of results.
  5101. - """
  5102. - search_url = ('http://%s/w/api.php?format=json&action=query'
  5103. - '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'
  5104. - '&srsearch=') % (server, num)
  5105. - search_url += query
  5106. - query = json.loads(web.get(search_url))
  5107. - if 'query' in query:
  5108. - query = query['query']['search']
  5109. - return [r['title'] for r in query]
  5110. - else:
  5111. - return None
  5112. -
  5113. -
  5114. -def say_snippet(bot, server, query, show_url=True):
  5115. - page_name = query.replace('_', ' ')
  5116. - query = query.replace(' ', '_')
  5117. - snippet = mw_snippet(server, query)
  5118. - msg = '[WIKIPEDIA] {} | "{}"'.format(page_name, snippet)
  5119. - if show_url:
  5120. - msg = msg + ' | https://{}/wiki/{}'.format(server, query)
  5121. - bot.say(msg)
  5122. -
  5123. -
  5124. -def mw_snippet(server, query):
  5125. - """
  5126. - Retrives a snippet of the specified length from the given page on the given
  5127. - server.
  5128. - """
  5129. - snippet_url = ('https://' + server + '/w/api.php?format=json'
  5130. - '&action=query&prop=extracts&exintro&explaintext'
  5131. - '&exchars=300&redirects&titles=')
  5132. - snippet_url += query
  5133. - snippet = json.loads(web.get(snippet_url))
  5134. - snippet = snippet['query']['pages']
  5135. -
  5136. - # For some reason, the API gives the page *number* as the key, so we just
  5137. - # grab the first page number in the results.
  5138. - snippet = snippet[list(snippet.keys())[0]]
  5139. -
  5140. - return snippet['extract']
  5141. -
  5142. -
  5143. -@rule('.*/([a-z]+\.wikipedia.org)/wiki/([^ ]+).*')
  5144. -def mw_info(bot, trigger, found_match=None):
  5145. - """
  5146. - Retrives a snippet of the specified length from the given page on the given
  5147. - server.
  5148. - """
  5149. - match = found_match or trigger
  5150. - say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)
  5151. -
  5152. -
  5153. -@commands('w', 'wiki', 'wik')
  5154. -@example('.w San Francisco')
  5155. -def wikipedia(bot, trigger):
  5156. - lang = bot.config.wikipedia.default_lang
  5157. -
  5158. - #change lang if channel has custom language set
  5159. - if (trigger.sender and not trigger.sender.is_nick() and
  5160. - bot.config.wikipedia.lang_per_channel):
  5161. - customlang = re.search('(' + trigger.sender + '):(\w+)',
  5162. - bot.config.wikipedia.lang_per_channel)
  5163. - if customlang is not None:
  5164. - lang = customlang.group(2)
  5165. -
  5166. - if trigger.group(2) is None:
  5167. - bot.reply("What do you want me to look up?")
  5168. - return NOLIMIT
  5169. -
  5170. - query = trigger.group(2)
  5171. - args = re.search(r'^-([a-z]{2,12})\s(.*)', query)
  5172. - if args is not None:
  5173. - lang = args.group(1)
  5174. - query = args.group(2)
  5175. -
  5176. - if not query:
  5177. - bot.reply('What do you want me to look up?')
  5178. - return NOLIMIT
  5179. - server = lang + '.wikipedia.org'
  5180. - query = mw_search(server, query, 1)
  5181. - if not query:
  5182. - bot.reply("I can't find any results for that.")
  5183. - return NOLIMIT
  5184. - else:
  5185. - query = query[0]
  5186. - say_snippet(bot, server, query)
  5187. diff --git a/sopel/modules/wiktionary.py b/sopel/modules/wiktionary.py
  5188. deleted file mode 100644
  5189. index 844181c..0000000
  5190. --- a/sopel/modules/wiktionary.py
  5191. +++ /dev/null
  5192. @@ -1,101 +0,0 @@
  5193. -# coding=utf-8
  5194. -"""
  5195. -wiktionary.py - Sopel Wiktionary Module
  5196. -Copyright 2009, Sean B. Palmer, inamidst.com
  5197. -Licensed under the Eiffel Forum License 2.
  5198. -
  5199. -http://sopel.chat
  5200. -"""
  5201. -from __future__ import unicode_literals, absolute_import, print_function, division
  5202. -
  5203. -import re
  5204. -from sopel import web
  5205. -from sopel.module import commands, example
  5206. -
  5207. -uri = 'http://en.wiktionary.org/w/index.php?title=%s&printable=yes'
  5208. -r_tag = re.compile(r'<[^>]+>')
  5209. -r_ul = re.compile(r'(?ims)<ul>.*?</ul>')
  5210. -
  5211. -
  5212. -def text(html):
  5213. - text = r_tag.sub('', html).strip()
  5214. - text = text.replace('\n', ' ')
  5215. - text = text.replace('\r', '')
  5216. - text = text.replace('(intransitive', '(intr.')
  5217. - text = text.replace('(transitive', '(trans.')
  5218. - return text
  5219. -
  5220. -
  5221. -def wikt(word):
  5222. - bytes = web.get(uri % web.quote(word))
  5223. - bytes = r_ul.sub('', bytes)
  5224. -
  5225. - mode = None
  5226. - etymology = None
  5227. - definitions = {}
  5228. - for line in bytes.splitlines():
  5229. - if 'id="Etymology"' in line:
  5230. - mode = 'etymology'
  5231. - elif 'id="Noun"' in line:
  5232. - mode = 'noun'
  5233. - elif 'id="Verb"' in line:
  5234. - mode = 'verb'
  5235. - elif 'id="Adjective"' in line:
  5236. - mode = 'adjective'
  5237. - elif 'id="Adverb"' in line:
  5238. - mode = 'adverb'
  5239. - elif 'id="Interjection"' in line:
  5240. - mode = 'interjection'
  5241. - elif 'id="Particle"' in line:
  5242. - mode = 'particle'
  5243. - elif 'id="Preposition"' in line:
  5244. - mode = 'preposition'
  5245. - elif 'id="' in line:
  5246. - mode = None
  5247. -
  5248. - elif (mode == 'etmyology') and ('<p>' in line):
  5249. - etymology = text(line)
  5250. - elif (mode is not None) and ('<li>' in line):
  5251. - definitions.setdefault(mode, []).append(text(line))
  5252. -
  5253. - if '<hr' in line:
  5254. - break
  5255. - return etymology, definitions
  5256. -
  5257. -parts = ('preposition', 'particle', 'noun', 'verb',
  5258. - 'adjective', 'adverb', 'interjection')
  5259. -
  5260. -
  5261. -def format(result, definitions, number=2):
  5262. - for part in parts:
  5263. - if part in definitions:
  5264. - defs = definitions[part][:number]
  5265. - result += u' — {}: '.format(part)
  5266. - n = ['%s. %s' % (i + 1, e.strip(' .')) for i, e in enumerate(defs)]
  5267. - result += ', '.join(n)
  5268. - return result.strip(' .,')
  5269. -
  5270. -
  5271. -@commands('wt', 'define', 'dict')
  5272. -@example('.wt bailiwick')
  5273. -def wiktionary(bot, trigger):
  5274. - """Look up a word on Wiktionary."""
  5275. - word = trigger.group(2)
  5276. - if word is None:
  5277. - bot.reply('You must tell me what to look up!')
  5278. - return
  5279. -
  5280. - _etymology, definitions = wikt(word)
  5281. - if not definitions:
  5282. - bot.say("Couldn't get any definitions for %s." % word)
  5283. - return
  5284. -
  5285. - result = format(word, definitions)
  5286. - if len(result) < 150:
  5287. - result = format(word, definitions, 3)
  5288. - if len(result) < 150:
  5289. - result = format(word, definitions, 5)
  5290. -
  5291. - if len(result) > 300:
  5292. - result = result[:295] + '[...]'
  5293. - bot.say(result)
  5294. diff --git a/sopel/modules/xkcd.py b/sopel/modules/xkcd.py
  5295. deleted file mode 100644
  5296. index d2baef9..0000000
  5297. --- a/sopel/modules/xkcd.py
  5298. +++ /dev/null
  5299. @@ -1,105 +0,0 @@
  5300. -# coding=utf-8
  5301. -# Copyright 2010, Michael Yanovich (yanovich.net), and Morgan Goose
  5302. -# Copyright 2012, Lior Ramati
  5303. -# Copyright 2013, Elsie Powell (embolalia.com)
  5304. -# Licensed under the Eiffel Forum License 2.
  5305. -from __future__ import unicode_literals, absolute_import, print_function, division
  5306. -
  5307. -import json
  5308. -import random
  5309. -import re
  5310. -import requests
  5311. -from sopel import web
  5312. -from sopel.modules.search import google_search
  5313. -from sopel.module import commands
  5314. -
  5315. -ignored_sites = [
  5316. - # For google searching
  5317. - 'almamater.xkcd.com',
  5318. - 'blog.xkcd.com',
  5319. - 'blag.xkcd.com',
  5320. - 'forums.xkcd.com',
  5321. - 'fora.xkcd.com',
  5322. - 'forums3.xkcd.com',
  5323. - 'store.xkcd.com',
  5324. - 'wiki.xkcd.com',
  5325. - 'what-if.xkcd.com',
  5326. -]
  5327. -sites_query = ' site:xkcd.com -site:' + ' -site:'.join(ignored_sites)
  5328. -
  5329. -
  5330. -def get_info(number=None):
  5331. - if number:
  5332. - url = 'http://xkcd.com/{}/info.0.json'.format(number)
  5333. - else:
  5334. - url = 'http://xkcd.com/info.0.json'
  5335. - data = requests.get(url).json()
  5336. - data['url'] = 'http://xkcd.com/' + str(data['num'])
  5337. - return data
  5338. -
  5339. -
  5340. -def google(query):
  5341. - url = google_search(query + sites_query)
  5342. - if not url:
  5343. - return None
  5344. - match = re.match('(?:https?://)?xkcd.com/(\d+)/?', url)
  5345. - if match:
  5346. - return match.group(1)
  5347. -
  5348. -
  5349. -@commands('xkcd')
  5350. -def xkcd(bot, trigger):
  5351. - """
  5352. - .xkcd - Finds an xkcd comic strip. Takes one of 3 inputs:
  5353. - If no input is provided it will return a random comic
  5354. - If numeric input is provided it will return that comic, or the nth-latest
  5355. - comic if the number is non-positive
  5356. - If non-numeric input is provided it will return the first google result for those keywords on the xkcd.com site
  5357. - """
  5358. - # get latest comic for rand function and numeric input
  5359. - latest = get_info()
  5360. - max_int = latest['num']
  5361. -
  5362. - # if no input is given (pre - lior's edits code)
  5363. - if not trigger.group(2): # get rand comic
  5364. - random.seed()
  5365. - requested = get_info(random.randint(1, max_int + 1))
  5366. - else:
  5367. - query = trigger.group(2).strip()
  5368. -
  5369. - numbered = re.match(r"^(#|\+|-)?(\d+)$", query)
  5370. - if numbered:
  5371. - query = int(numbered.group(2))
  5372. - if numbered.group(1) == "-":
  5373. - query = -query
  5374. - if query > max_int:
  5375. - bot.say(("Sorry, comic #{} hasn't been posted yet. "
  5376. - "The last comic was #{}").format(query, max_int))
  5377. - return
  5378. - elif query <= -max_int:
  5379. - bot.say(("Sorry, but there were only {} comics "
  5380. - "released yet so far").format(max_int))
  5381. - return
  5382. - elif abs(query) == 0:
  5383. - requested = latest
  5384. - elif query == 404 or max_int + query == 404:
  5385. - bot.say("404 - Not Found") # don't error on that one
  5386. - return
  5387. - elif query > 0:
  5388. - requested = get_info(query)
  5389. - else:
  5390. - # Negative: go back that many from current
  5391. - requested = get_info(max_int + query)
  5392. - else:
  5393. - # Non-number: google.
  5394. - if (query.lower() == "latest" or query.lower() == "newest"):
  5395. - requested = latest
  5396. - else:
  5397. - number = google(query)
  5398. - if not number:
  5399. - bot.say('Could not find any comics for that query.')
  5400. - return
  5401. - requested = get_info(number)
  5402. -
  5403. - message = '{} [{}]'.format(requested['url'], requested['title'])
  5404. - bot.say(message)
  5405. diff --git a/sopel/trigger.py b/sopel/trigger.py
  5406. index 1a03744..a91d5c4 100644
  5407. --- a/sopel/trigger.py
  5408. +++ b/sopel/trigger.py
  5409. @@ -50,9 +50,9 @@ class PreTrigger(object):
  5410.  
  5411. # TODO note what this is doing and why
  5412. if ' :' in line:
  5413. - argstr, text = line.split(' :', 1)
  5414. + argstr, self.text = line.split(' :', 1)
  5415. self.args = argstr.split(' ')
  5416. - self.args.append(text)
  5417. + self.args.append(self.text)
  5418. else:
  5419. self.args = line.split(' ')
  5420. self.text = self.args[-1]
  5421. @@ -141,6 +141,7 @@ class Trigger(unicode):
  5422. ``('#example', '-m')``
  5423. """
  5424. tags = property(lambda self: self._pretrigger.tags)
  5425. + text = property(lambda self: self._pretrigger.text)
  5426. """A map of the IRCv3 message tags on the message."""
  5427. admin = property(lambda self: self._admin)
  5428. """True if the nick which triggered the command is one of the bot's admins.
  5429. --
  5430. 2.4.6
  5431.  
  5432.  
  5433. From 4444bd1d630f85f6937a85b2c37ef1c7c7badf23 Mon Sep 17 00:00:00 2001
  5434. From: S00ng <???>
  5435. Date: Sun, 1 May 2016 15:22:08 +0200
  5436. Subject: [PATCH 2/4] init machine learning
  5437.  
  5438. ---
  5439. sopel/modules/helloworld.py | 18 ++++++++++++++++++
  5440. sopeldatawriter.py | 10 ++++++++++
  5441. 2 files changed, 28 insertions(+)
  5442. create mode 100644 sopel/modules/helloworld.py
  5443. create mode 100644 sopeldatawriter.py
  5444.  
  5445. diff --git a/sopel/modules/helloworld.py b/sopel/modules/helloworld.py
  5446. new file mode 100644
  5447. index 0000000..a7553cf
  5448. --- /dev/null
  5449. +++ b/sopel/modules/helloworld.py
  5450. @@ -0,0 +1,18 @@
  5451. +from sopel import module
  5452. +import sopeldatawriter
  5453. +
  5454. +def check(bot, trigger):
  5455. + sopeldatawriter.user_data_write_csv(trigger.nick,trigger.text,"testfile.csv")
  5456. + '''
  5457. [email protected]('hello|(hello\s*(everyone|d4t4))')
  5458. +def hi(bot, trigger):
  5459. + bot.say('Hello, ' + trigger.nick)
  5460. [email protected]('[a-zA-Z0-9_\s]*d4t4[a-zA-Z0-9_\s]*new[a-zA-Z0-9_\s]*here[a-zA-Z0-9_\s]*')
  5461. +def new(bot, trigger):
  5462. + bot.say('Yes I am new here')
  5463. [email protected]('\s*\w*\s*d4t4\s*\w*\s*introduce\s*\w*\s*yourself\s*\w*\s*')
  5464. +def introduce(bot, trigger):
  5465. + bot.say('My Name is D4t4. I am 1 day old, I have no gender , I am programmed from s00ng')
  5466. + bot.say('I am a sopel module')
  5467. +'''
  5468. diff --git a/sopeldatawriter.py b/sopeldatawriter.py
  5469. new file mode 100644
  5470. index 0000000..77910cb
  5471. --- /dev/null
  5472. +++ b/sopeldatawriter.py
  5473. @@ -0,0 +1,10 @@
  5474. +import csv
  5475. +
  5476. +def user_data_write_csv(nick,text,file):
  5477. + with open(file, 'a') as csvfile:
  5478. + csvwriter = csv.writer(csvfile, delimiter=' ',quotechar='|', quoting=csv.QUOTE_MINIMAL)
  5479. + csvwriter.writerow([nick,text])
  5480. +def user_data_write_raw(nick,text,file):
  5481. + with open(file, "a") as f:
  5482. + data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
  5483. + f.write(data)
  5484. --
  5485. 2.4.6
  5486.  
  5487.  
  5488. From 89c614e45522e8c6066b7d006d874b8f68e4342d Mon Sep 17 00:00:00 2001
  5489. From: s00ng <???>
  5490. Date: Fri, 3 Jun 2016 22:18:16 +0200
  5491. Subject: [PATCH 3/4] fix KeyError with multiple channels
  5492.  
  5493. ---
  5494. sopel/tools/target.py | 9 +++++----
  5495. sopeldatawriter.py | 1 +
  5496. 2 files changed, 6 insertions(+), 4 deletions(-)
  5497.  
  5498. diff --git a/sopel/tools/target.py b/sopel/tools/target.py
  5499. index 2ff0a91..963a439 100644
  5500. --- a/sopel/tools/target.py
  5501. +++ b/sopel/tools/target.py
  5502. @@ -62,10 +62,11 @@ class Channel(object):
  5503. """The topic of the channel."""
  5504.  
  5505. def clear_user(self, nick):
  5506. - user = self.users[nick]
  5507. - user.channels.pop(self.name, None)
  5508. - del self.users[nick]
  5509. - del self.privileges[nick]
  5510. + if nick in self.users:
  5511. + user = self.users[nick]
  5512. + user.channels.pop(self.name, None)
  5513. + del self.users[nick]
  5514. + del self.privileges[nick]
  5515.  
  5516. def add_user(self, user):
  5517. assert isinstance(user, User)
  5518. diff --git a/sopeldatawriter.py b/sopeldatawriter.py
  5519. index 77910cb..47b77f1 100644
  5520. --- a/sopeldatawriter.py
  5521. +++ b/sopeldatawriter.py
  5522. @@ -4,6 +4,7 @@ def user_data_write_csv(nick,text,file):
  5523. with open(file, 'a') as csvfile:
  5524. csvwriter = csv.writer(csvfile, delimiter=' ',quotechar='|', quoting=csv.QUOTE_MINIMAL)
  5525. csvwriter.writerow([nick,text])
  5526. + csvfile.close()
  5527. def user_data_write_raw(nick,text,file):
  5528. with open(file, "a") as f:
  5529. data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
  5530. --
  5531. 2.4.6
  5532.  
  5533.  
  5534. From ca1568d577018943fd22aab83129cc58ab45a0e8 Mon Sep 17 00:00:00 2001
  5535. From: s00ng <???>
  5536. Date: Fri, 3 Jun 2016 22:20:19 +0200
  5537. Subject: [PATCH 4/4] close the file after write in it
  5538.  
  5539. ---
  5540. sopeldatawriter.py | 1 +
  5541. 1 file changed, 1 insertion(+)
  5542.  
  5543. diff --git a/sopeldatawriter.py b/sopeldatawriter.py
  5544. index 47b77f1..ea4d50f 100644
  5545. --- a/sopeldatawriter.py
  5546. +++ b/sopeldatawriter.py
  5547. @@ -9,3 +9,4 @@ def user_data_write_raw(nick,text,file):
  5548. with open(file, "a") as f:
  5549. data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
  5550. f.write(data)
  5551. + f.close()
  5552. --
  5553. 2.4.6
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement