~aleteoryx/sparfnord

ref: 6c71e63bb9ef7553e5bc9375cdb8fa5a906e00bd sparfnord/sparf.py -rw-r--r-- 3.3 KiB
6c71e63bAleteoryx that's the bot 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import discord

import json
import logging
import logging.config
from pathlib import Path
from dataclasses import dataclass
from sys import argv, stderr, exit


STAR = '⭐'

log = logging.getLogger('sparf')
logging.basicConfig(stream=stderr, level=logging.INFO, format='[%(asctime)s %(levelname)s %(name)s] %(msg)s')


argv0 = 'sparf.py'
def usage():
  print(f'usage: {argv0} config.json', file=stderr)
  exit(-1)


class ConfigError(Exception):
  pass

class Config:
  def __init__(self, path):
    self.log = logging.getLogger('sparf.config')
    self.path = path
    self.load()

    if 'guilds' not in self.data:
      self.data['guilds'] = {}
      self.save()

    if 'token' not in self.data:
      raise ConfigError('missing config.token!')

  def __setitem__(self, k, v):
    self.data[k] = v
  def __getitem__(self, k):
    return self.data[k]
  def __contains__(self, k):
    return k in self.data

  def load(self):
    self.log.debug('reloading config...')
    with open(self.path, 'rt') as fp:
      self.data = json.load(fp)
    self.log.debug('config loaded!')

  def save(self):
    self.log.debug('saving config...')
    with open(self.path, 'wt') as fp:
      json.dump(self.data, fp, indent=2)
    self.log.debug('config saved!')


cli = discord.Bot(intents=discord.Intents.default())


@cli.event
async def on_raw_reaction_add(evt):
  if evt.emoji.name != STAR:
    return

  if str(evt.guild_id) not in config['guilds']:
    return
  gconf = config['guilds'][str(evt.guild_id)]
  if 'threshold' not in gconf or 'channel' not in gconf:
    log.info(f'would log {evt.message_id} in {evt.guild_id}, but I don\'t know where!')
    return
  thresh = gconf['threshold']
  chan = cli.get_partial_messageable(int(gconf['channel']))

  if chan.id == evt.channel_id:
    return

  msg = cli.get_message(evt.message_id)
  if msg is None:
    srcchan = cli.get_partial_messageable(evt.channel_id)
    msg = await srcchan.fetch_message(evt.message_id)

  for reaction in msg.reactions:
    if reaction.emoji == STAR and reaction.count >= thresh:
      async for who in reaction.users():
        if who.id == cli.application_id:
          return

      fwd = await msg.forward_to(chan)
      await chan.send(msg.author.mention, reference=fwd.to_reference())
      await msg.add_reaction(STAR)
      log.info(f'logged {evt.message_id} in {evt.guild_id}')
      return

@cli.slash_command(description='Configure the bot.')
@discord.default_permissions(administrator=True)
async def setup(ctx, threshold: discord.Option(int, description='# of stars needed'), channel: discord.Option(discord.TextChannel, description='where to forward messages')):
  if str(ctx.guild_id) not in config['guilds']:
    config['guilds'][str(ctx.guild_id)] = {}

  config['guilds'][str(ctx.guild_id)]['threshold'] = threshold
  config['guilds'][str(ctx.guild_id)]['channel'] = str(channel.id)
  config.save()

  log.info(f'/setup in {ctx.guild_id}: thresh={threshold} channel={channel.id}')
  await ctx.send_response(f'Okay! Threshold is {threshold} reacts and channel is {channel.mention}.', ephemeral=True)


if len(argv) > 0:
  argv0 = argv[0]
  argv = argv[1:]
if len(argv) != 1:
  usage()

path = Path(argv[0])
if not path.exists():
  print(f'fatal: {str(path)} does not exist', file=stderr)
  exit(-2)

config = Config(path)
cli.run(config['token'])