├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── bot.py ├── bot ├── __init__.py ├── commands.py ├── core.py └── messages.py ├── constants ├── __init__.py └── core.py ├── examples ├── common │ ├── graph.css │ ├── graph.js │ └── index.html ├── data │ ├── metagame.js │ ├── raid_guild-s0.js │ ├── raid_guild-s1.js │ ├── raid_guild-s2.js │ ├── raid_guild-s3.js │ ├── raid_guild-s4.js │ └── simple.js ├── index.md ├── metagame │ └── index.html ├── raidguild-s0 │ └── index.html ├── raidguild-s1 │ └── index.html ├── raidguild-s2 │ └── index.html ├── raidguild-s3 │ └── index.html ├── raidguild-s4 │ └── index.html └── simple │ └── index.html ├── export.py ├── misc └── 2021-metafest.md ├── model ├── __init__.py └── core.py ├── render ├── __init__.py └── core.py ├── repository ├── __init__.py └── core.py ├── requirements.system ├── requirements.txt ├── sample ├── full-graph.jpg ├── interest.jpg ├── metagame.jpg ├── overlap.jpg ├── self.jpg ├── skill.jpg └── word-cloud.jpg ├── test.py └── tmp └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__ 3 | *.pyc 4 | .venv 5 | .env 6 | *.dot.png 7 | *.dot.jpg 8 | *.dot 9 | *.db 10 | *.sqlite 11 | *-export.json 12 | *-export.js 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 www.metagame.wtf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Skill Discovery (Discord bot) 2 | 3 | This little project is a "hack" developed as part of [MetaFest](https://metafest.metagame.wtf) 2021. 4 | 5 | ### Objective 6 | 7 | Create a graphical representation of people skills and interests. 8 | 9 | ### Why? 10 | 11 | To facilitate social bonding and skills exchange: 12 | 13 | * Find someone that has skill S, which you are looking for 14 | * Find what skills/interests you have in common with person P 15 | * Get a general feel of the interests within a community 16 | 17 | ### Examples 18 | 19 | Some interactive visualizations of the data are available [here](https://metafam.github.io/skill-bot/examples): 20 | 21 | Full graph, including all people, all skills/interests and their links: 22 | 23 | ![Full graph](sample/full-graph.jpg) 24 | 25 | Word-cloud view of the above: 26 | 27 | ![Word cloud](sample/word-cloud.jpg) 28 | 29 | ## Project structure 30 | 31 | #### ⚠️ HACK ALERT ⚠️ 32 | The source code of this project is **hack-level** quality. 33 | 34 | It was stringed together in less than a week. 35 | Please calibrate your expectations accordingly. 36 | 37 | For example: 38 | 39 | * ❌ Most of the code is not covered by tests 40 | * ❌ Code is a hodgepodge of classes, static functions, globals, etc (thanks, Python!) 41 | * ❌ Some calls are not thread-safe (database, graph rendering, ...) 42 | * ❌ The data model is as simple as it gets 43 | * ❌ The rendering is pretty rudimentary 44 | * etc. 45 | 46 | That said, it's in pretty good shape *for a hack*. 47 | Future improvements (if anyone is willing) should be possible incrementally. 48 | 49 | ### Code overview 50 | 51 | ... 52 | 53 | ## Deployment 54 | 55 | The bot requires 3 environment variables to run properly. 56 | They can be set on the shell, or in a `.env` file. 57 | 58 | `DISCORD_TOKEN`: Token obtained by creating a new discord application. 59 | For examples, see [this guide](https://realpython.com/how-to-make-a-discord-bot-python/) or similar. 60 | 61 | `GUILD_ID`: The unique numeric id of the Discord server (a.k.a guild). 62 | 63 | `MONITOR_CHANNEL_ID`: The unique numeric ID of the Discord channel to monitor. 64 | 65 | The easiest way to learn your server and channel IDs is to open Discord in a browser. 66 | The URL will look like this: 67 | 68 | > https://discord.com/channels/// 69 | 70 | ### Dependencies 71 | 72 | **Python 3.9** and it's dependencies listed in `requirements.txt`. 73 | System (apt package) requirements are listed in requirements.system. Each of these can be installed with `sudo apt install` or equivalent commands. 74 | 75 | Using a Python virtual environment is recommended. 76 | Alternatively, using `nix`: 77 | 78 | > nix-shell -p 'python39.withPackages(ps: with ps; [ graphviz python-dotenv discordpy ])' 79 | 80 | The `dot` program must be installed (on Linux and macOS, this is part of the `graphviz` package). You can learn more about this format on [Wikipedia](https://en.wikipedia.org/wiki/Graphviz) or on the [official website](https://www.graphviz.org/). 81 | 82 | Additional fonts may be necessary to properly display emojis on Linux. Consider installing the `fonts-noto` package. Additional fonts for use with graphviz can also be installed via fontconfig (see https://gist.github.com/jacksonpradolima/840b4a20c617204a65b15da61e124bf6). 83 | 84 | A sample installation of dependencies on an Ubuntu distro: 85 | 86 | 1) `sudo xargs apt install <./requirements.system` installs system requirements (python, graphviz, etc) 87 | 2) `python3.9 -m venv ./.venv` creates a virtual environment 88 | 3) `source .venv/bin/activate` activates your virtual environment 89 | 4) `pip install -r ./requirements.txt` installs all requisite python packages into your virtual environment 90 | 91 | ### Running locally 92 | 93 | Once you have gone through the above steps to creating a new discord app/bot, and have installed necessary depdendencies, you'll want to test the bot locally. 94 | 95 | Following the steps below will spin up the bot on your machine, and it'll be listening in the discord server you configured with the `DISCORD_TOKEN`, `GUILD_ID`, and `MONITOR_CHANNEL_ID`. It'll also spin up a SQLite database in the root directory where you're running the bot, to store the skills and users created via the Discord CLI. 96 | 97 | The following steps will start the bot on your machine: 98 | 1) `source ./.venv/bin/activate` 99 | 2) `python3.9 bot.py` 100 | 101 | That's it! 102 | 103 | ### Permissions 104 | 105 | To work properly, the bot needs the following permission in the target channel: 106 | 107 | * Read channel messages 108 | * Write messages to channel 109 | * React to messages in channel 110 | 111 | ## License 112 | 113 | This project is released under `MIT` license. See the `LICENSE` file for details. 114 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | # bot.py 2 | import os 3 | 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | TOKEN = os.getenv('DISCORD_TOKEN') 8 | GUILD = int(os.getenv('GUILD_ID')) 9 | CHANNEL = int(os.getenv('MONITOR_CHANNEL_ID')) 10 | DB_FILE = f'{GUILD}-{CHANNEL}.sqlite' 11 | CUSTOMIZATION = os.getenv('CUSTOMIZATION') 12 | 13 | import constants 14 | if CUSTOMIZATION == "metagame": 15 | constants.apply_metagame_customization() 16 | elif CUSTOMIZATION == "raidguild": 17 | constants.apply_raidguild_customization() 18 | else: 19 | constants.apply_generic_string_formatting() 20 | 21 | 22 | import bot 23 | import repository 24 | 25 | repo = repository.SqliteRepository(DB_FILE) 26 | 27 | commands = [ 28 | bot.HelpCommand, 29 | bot.InfoCommand, 30 | bot.ListSkillsCommand, 31 | bot.StatsCommand, 32 | bot.AddSkillCommand, 33 | bot.DrawFullGraphCommand, 34 | bot.DrawWordCloudCommand, 35 | bot.DrawPeopleSubgraphCommand, 36 | bot.DrawSkillsSubgraphCommand, 37 | ] 38 | 39 | bot.Controller(GUILD, CHANNEL, repo, commands).run(TOKEN) 40 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | from .commands import * 3 | -------------------------------------------------------------------------------- /bot/commands.py: -------------------------------------------------------------------------------- 1 | import re 2 | from discord import File 3 | 4 | from constants import Strings as k 5 | from . import messages 6 | 7 | class AbstractCommand(object): 8 | """Base class for commands""" 9 | 10 | def __init__(self, message): 11 | super(AbstractCommand, self).__init__() 12 | self.message = message 13 | 14 | async def execute(self, client): 15 | raise Exception(f'Execute not implemented for {self}') 16 | 17 | class HelpCommand(AbstractCommand): 18 | """Responds with a message with bot usage info and commands""" 19 | 20 | _name = "help" 21 | _description = "Display bot usage and help" 22 | _example_arguments = None 23 | 24 | def __init__(self, message, args): 25 | super(HelpCommand, self).__init__(message) 26 | 27 | async def execute(self, client): 28 | await self.message.channel.send( 29 | embed=messages.help_message(client.get_commands()) 30 | ) 31 | 32 | class InfoCommand(AbstractCommand): 33 | """Responds with a message with bot information""" 34 | 35 | _name = "info" 36 | _description = "Display bot info" 37 | _example_arguments = None 38 | 39 | def __init__(self, message, args): 40 | super(InfoCommand, self).__init__(message) 41 | 42 | async def execute(self, client): 43 | await self.message.channel.send(embed=messages.INFO) 44 | 45 | 46 | class AddSkillCommand(AbstractCommand): 47 | """Creates a new skill in the graph""" 48 | 49 | _name = "new" 50 | _description = f'Creates new {k.ENTITY_SHORT}' 51 | _example_arguments = k.NEW_COMMAND_EXAMPLES 52 | _skill_re = re.compile(k.NEW_SKILL_REGEX) 53 | 54 | def __init__(self, message, args): 55 | super(AddSkillCommand, self).__init__(message) 56 | print(f'🐛 Args: {args}') 57 | if not args: 58 | raise Exception("Missing skill name :shrug:") 59 | skill_name = re.sub(r'\W+', '_', args) 60 | print(f'🐛 skill_name: {skill_name}') 61 | if not AddSkillCommand._skill_re.match(skill_name): 62 | raise Exception(f'Invalid {k.ENTITY_SHORT} name :shrug:') 63 | self.skill_name = skill_name.lower() 64 | 65 | async def execute(self, client): 66 | await client.create_skill(self.skill_name) 67 | 68 | class ListSkillsCommand(AbstractCommand): 69 | """A summary message with all skills""" 70 | 71 | _name = "list" 72 | _description = f'List all {k.ENTITIES_LONG}' 73 | _example_arguments = None 74 | 75 | def __init__(self, message, args): 76 | super(ListSkillsCommand, self).__init__(message) 77 | 78 | async def execute(self, client): 79 | await client.send_skill_recap_message(self.message.channel) 80 | 81 | class StatsCommand(AbstractCommand): 82 | """A summary message with entities count""" 83 | 84 | _name = "stats" 85 | _description = "Display statistics" 86 | _example_arguments = None 87 | 88 | def __init__(self, message, args): 89 | super(StatsCommand, self).__init__(message) 90 | 91 | async def execute(self, client): 92 | await client.send_stats_message(self.message.channel) 93 | 94 | class DrawFullGraphCommand(AbstractCommand): 95 | """Creates an image of the whole graph""" 96 | 97 | _name = "fullgraph" 98 | _description = f'Draws the full graph of {k.PEOPLE} and {k.ENTITIES_LONG}' 99 | _example_arguments = None 100 | 101 | def __init__(self, message, args): 102 | super(DrawFullGraphCommand, self).__init__(message) 103 | 104 | async def execute(self, client): 105 | from render import ImageFileRenderer, FullGraphDotRenderer 106 | dot_graph = FullGraphDotRenderer(client.get_graph_snapshot()).render() 107 | png_file = ImageFileRenderer(dot_graph).render() 108 | 109 | m = await self.message.channel.send( 110 | embed=messages.FULL_GRAPH, 111 | file=File(png_file) 112 | ) 113 | 114 | class DrawWordCloudCommand(AbstractCommand): 115 | """Creates an word cloud image for all interests""" 116 | 117 | _name = "wordcloud" 118 | _description = f'Creates a word-cloud of {k.ENTITIES_LONG}' 119 | _example_arguments = None 120 | 121 | def __init__(self, message, args): 122 | super(DrawWordCloudCommand, self).__init__(message) 123 | 124 | async def execute(self, client): 125 | from render import ImageFileRenderer, WordCloudDotRenderer 126 | dot_graph = WordCloudDotRenderer(client.get_graph_snapshot()).render() 127 | png_file = ImageFileRenderer(dot_graph).render() 128 | 129 | m = await self.message.channel.send( 130 | embed=messages.WORD_CLOUD_GRAPH, 131 | file=File(png_file) 132 | ) 133 | 134 | class DrawPeopleSubgraphCommand(AbstractCommand): 135 | """Creates an image of the graph with a subset of people explicitly requested""" 136 | 137 | _name = k.PEOPLE 138 | _description = f'Draw graph with just a subset of {k.PEOPLE}' 139 | _example_arguments = ["@Adam @Bob @Charlie"] 140 | 141 | def __init__(self, message, args): 142 | super(DrawPeopleSubgraphCommand, self).__init__(message) 143 | if not args: 144 | raise Exception(f"{k.COMMAND_PREFIX} {k.PEOPLE} had no {k.PEOPLE} supplied!") 145 | mentions = re.findall('<@(?:!)?(\d+)>', args) 146 | print(f'🐛 mentions: {mentions}') 147 | if not mentions: 148 | ex_msg = f"{k.DRAW_PEOPLE_ERROR_MESSAGE} _Example_ `{k.COMMAND_PREFIX} {k.PEOPLE} {self._example_arguments[0]}`" 149 | raise Exception(ex_msg) 150 | self.people_ids = [int(m) for m in mentions] 151 | 152 | async def execute(self, client): 153 | from render import ImageFileRenderer, SubGraphDotRenderer 154 | dot_graph = SubGraphDotRenderer(client.get_people_subgraph_snapshot(self.people_ids)).render() 155 | png_file = ImageFileRenderer(dot_graph).render() 156 | 157 | m = await self.message.channel.send( 158 | embed=messages.people_subgraph_message(self.people_ids), 159 | file=File(png_file) 160 | ) 161 | 162 | class DrawSkillsSubgraphCommand(AbstractCommand): 163 | """Creates an image of the graph with a subset of skills explicitly requested""" 164 | 165 | _name = k.DRAW_SKILLS_COMMAND_NAME 166 | _description = f'Draw graph with just a subset of {k.ENTITIES_LONG}' 167 | _example_arguments = k.DRAW_SKILLS_COMMAND_EXAMPLES 168 | 169 | def __init__(self, message, args): 170 | super(DrawSkillsSubgraphCommand, self).__init__(message) 171 | if not args: 172 | raise Exception(f'No {k.ENTITY_SHORT} supplied!') 173 | terms = [re.sub(r'\W+', '_', term) for term in args.split()] 174 | print(f'🐛 terms: {terms}') 175 | if not terms: 176 | raise Exception(f'{k.DRAW_SKILLS_ERROR_MESSAGE}. {k.ENTITY_SHORT} supplied ({repr(terms)}) not recognized!') 177 | self.terms = terms 178 | 179 | async def execute(self, client): 180 | skill_ids = client.search_skills(self.terms) 181 | from render import ImageFileRenderer, SubGraphDotRenderer 182 | dot_graph = SubGraphDotRenderer(client.get_skills_subgraph_snapshot(skill_ids)).render() 183 | png_file = ImageFileRenderer(dot_graph).render() 184 | 185 | m = await self.message.channel.send( 186 | embed=messages.skills_subgraph_message(self.terms), 187 | file=File(png_file) 188 | ) 189 | -------------------------------------------------------------------------------- /bot/core.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | import re 3 | import traceback 4 | import discord 5 | 6 | from constants import Strings as k 7 | from . import messages 8 | 9 | class Controller(discord.Client): 10 | """Top level controller for Skill Bot""" 11 | def __init__(self, guild_id: int, channel_id: int, repository, commands): 12 | super().__init__() 13 | self.channel_id = channel_id 14 | self.guild_id = guild_id 15 | self.repository = repository 16 | self.commands_map = {} 17 | for c in commands: 18 | self.commands_map[c._name] = c 19 | print( 20 | 'Starting...\n' 21 | f'Guild (a.k.a. server): {self.guild_id}\n' 22 | f'Watching channel: {self.channel_id}\n' 23 | ) 24 | self.repository.print_stats() 25 | 26 | async def on_ready(self): 27 | print(f'🐛 {self.user} has connected to Discord!') 28 | for g in self.guilds: 29 | print( 30 | f'{self.user} connected to: {g.name} (id: {g.id})' 31 | ) 32 | if g.id == self.guild_id: 33 | await g.get_channel(self.channel_id).send(embed=messages.HELLO) 34 | return 35 | raise Exception(f'Guild not found: {self.guild_id}') 36 | 37 | async def on_message(self, message): 38 | if self.is_command(message): 39 | print(f'🐛 Command: {message.content}') 40 | try: 41 | command = self.parse_command(message) 42 | await command.execute(self) 43 | except Exception as e: 44 | print(f'⚠️ Error handling message: "{message.content}":\n' + traceback.format_exc()) 45 | await message.channel.send( 46 | embed=messages.command_error_message(message.content, e) 47 | ) 48 | 49 | async def on_raw_reaction_add(self, payload): 50 | if not payload.member.bot and self.is_relevant_reaction(payload): 51 | print("🐛 + Reaction") 52 | self.repository.add_person(payload.member.id, payload.member.name) 53 | self.repository.add_person_skill(payload.member.id, payload.message_id) 54 | 55 | async def on_raw_reaction_remove(self, payload): 56 | if self.is_relevant_reaction(payload): 57 | print("🐛 - Reaction") 58 | self.repository.remove_person_skill(payload.user_id, payload.message_id) 59 | 60 | def is_command(self, message): 61 | return ( 62 | not message.author.bot and 63 | message.guild.id == self.guild_id and 64 | message.channel.id == self.channel_id and 65 | message.content.startswith(k.COMMAND_PREFIX) 66 | ) 67 | 68 | def is_relevant_reaction(self, payload): 69 | # print("🐛 reaction payload: " + str(payload)) 70 | return ( 71 | payload.guild_id == self.guild_id and 72 | payload.channel_id == self.channel_id and 73 | payload.emoji.name in k.ACCEPTED_REACTIONS and 74 | self.repository.skill_exists(payload.message_id) 75 | ) 76 | 77 | async def create_skill(self, skill_name: str): 78 | sid = self.repository.find_skill(skill_name) 79 | if sid: 80 | print("🐛 Duplicate skill: " + skill_name) 81 | await self.get_channel(self.channel_id).send( 82 | embed=messages.duplicate_skill_message(skill_name, sid, self.guild_id, self.channel_id) 83 | ) 84 | else: 85 | m = await self.get_channel(self.channel_id).send( 86 | embed=messages.new_skill_message(skill_name) 87 | ) 88 | self.repository.add_skill(m.id, skill_name) 89 | await m.add_reaction(k.REACTION) 90 | 91 | 92 | _command_re = re.compile(f'^{k.COMMAND_PREFIX} ([a-z]+)( .*)?$') 93 | 94 | def parse_command(self, message): 95 | match = Controller._command_re.match(message.content) 96 | if not match: 97 | raise Exception(f'Unrecognized command, Try `{k.COMMAND_PREFIX} help`.') 98 | 99 | command_name = match.group(1) 100 | command_args = match.group(2) 101 | if command_args: 102 | command_args = command_args.strip() 103 | command_class = self.commands_map.get(command_name) 104 | 105 | if not command_class: 106 | raise Exception(f'Unrecognized command, Try `{k.COMMAND_PREFIX} help`.') 107 | 108 | return command_class(message, command_args) 109 | 110 | def get_commands(self): 111 | return self.commands_map.values() 112 | 113 | def get_graph_snapshot(self): 114 | return self.repository.get_graph_snapshot() 115 | 116 | def get_people_subgraph_snapshot(self, people_ids: Iterable[int]): 117 | return self.repository.get_people_subgraph_snapshot(people_ids) 118 | 119 | def get_skills_subgraph_snapshot(self, skill_ids: Iterable[int]): 120 | return self.repository.get_skills_subgraph_snapshot(skill_ids) 121 | 122 | def search_skills(self, skill_terms: Iterable[str]) -> Iterable[int]: 123 | skill_ids = set() 124 | for skill_term in skill_terms: 125 | for skill in self.repository.find_skills_by_name(skill_term): 126 | skill_ids.add(skill.id) 127 | return skill_ids 128 | 129 | async def send_skill_recap_message(self, channel): 130 | skills = list(self.get_graph_snapshot().skills.values()) 131 | for embed_message in messages.paginated_list_messages(self.guild_id, self.channel_id, skills): 132 | await channel.send(embed=embed_message) 133 | 134 | async def send_stats_message(self, channel): 135 | pcount = self.repository.get_people_count() 136 | scount = self.repository.get_skills_count() 137 | pscount = self.repository.get_people_skills_count() 138 | await channel.send( 139 | embed=messages.stats_message(pcount, scount, pscount) 140 | ) 141 | -------------------------------------------------------------------------------- /bot/messages.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | 3 | import discord 4 | from constants import Strings as k 5 | from constants import Cosmetics as c 6 | 7 | HELLO = discord.Embed( 8 | title = k.HELLO_TITLE, 9 | colour = c.DISCORD_MESSAGE_COLOR, 10 | description = k.DESCRIPTION + f''' 11 | 12 | Try `{k.COMMAND_PREFIX} help` to learn more. 13 | ''' 14 | ) 15 | 16 | INFO = discord.Embed( 17 | title = k.INFO_MESSAGE_TITLE, 18 | colour = c.DISCORD_MESSAGE_COLOR, 19 | description = k.INFO_MESSAGE_DESCRIPTION 20 | ) 21 | 22 | FULL_GRAPH = discord.Embed( 23 | title = k.MAP_MESSAGE_TITLE, 24 | colour = c.DISCORD_MESSAGE_COLOR, 25 | description = f'It includes *all* {k.PEOPLE} and all {k.ENTITIES_LONG}' 26 | ) 27 | 28 | WORD_CLOUD_GRAPH = discord.Embed( 29 | title = k.WORD_CLOUD_MESSAGE_TITLE, 30 | colour = c.DISCORD_MESSAGE_COLOR, 31 | description = f'Words size is proportional to {k.WORD_CLOUD_SIZE}' 32 | ) 33 | 34 | def command_error_message(command, e): 35 | exMsg = repr(e) 36 | exMsg = exMsg.replace("Exception(", "").rstrip(')') 37 | return discord.Embed( 38 | title = k.COMMAND_ERROR_MESSAGE, 39 | colour = c.DISCORD_MESSAGE_COLOR, 40 | description = f''' 41 | Something went wrong with command: `{command}` 42 | 43 | {exMsg} 44 | 45 | **If you think this is a bug**, give a shout to {k.ERROR_CONTACT_PERSON} 46 | ''' 47 | ) 48 | 49 | def new_skill_message(skill_name): 50 | return discord.Embed( 51 | title = f'{skill_name}', 52 | colour = c.DISCORD_MESSAGE_COLOR, 53 | description = f''' 54 | Our guild has new {k.ENTITY_SHORT}! 55 | 56 | **React with {k.REACTION} to this message if you {k.REACTION_REASON}** 57 | ''' 58 | ) 59 | 60 | def duplicate_skill_message(skill_name: str, skill_id: int, guild_id: int, channel_id: int): 61 | return discord.Embed( 62 | title = f'Duplicate {skill_name}', 63 | colour = c.DISCORD_MESSAGE_COLOR, 64 | description = k.DUPLICATE_SKILL_ERROR_MESSAGE.format(guild_id, channel_id, skill_id) 65 | ) 66 | 67 | def help_message(commands): 68 | embed = discord.Embed( 69 | title = k.HELP_MESSAGE_TITLE, 70 | colour = c.DISCORD_MESSAGE_COLOR, 71 | description = f''' 72 | Use these commands to create new {k.ENTITY_SHORT}. 73 | Or visualize the current state of {k.ENTITIES_LONG} and {k.PEOPLE} connected to them. 74 | 75 | To associate yourself with exsiting {k.ENTITY_SHORT}, react with {k.REACTION} to the corresponding bot message. 76 | ''' 77 | ) 78 | for command in commands: 79 | command_help_message(command, embed) 80 | return embed 81 | 82 | def command_help_message(c, embed): 83 | command_string = f'`{k.COMMAND_PREFIX} {c._name}`' 84 | description = c._description 85 | if c._example_arguments: 86 | for ex in c._example_arguments: 87 | description += f'\n*Example*: `{k.COMMAND_PREFIX} {c._name} {ex}`' 88 | embed.add_field(name=command_string, value=description) 89 | return embed 90 | 91 | def list_message(guild_id: int, channel_id: int, skills: Iterable, num_pages: int, current_page: int): 92 | embed = discord.Embed( 93 | title = k.LIST_MESSAGE_TITLE.format(current_page, num_pages), 94 | colour = c.DISCORD_MESSAGE_COLOR, 95 | description = k.LIST_MESSAGE_DESCRIPTION 96 | ) 97 | for skill in skills: 98 | name_link=f'{k.SKILL_ICON} {skill.name}' 99 | description=f''' 100 | {len(skill.people)} {k.PEOPLE} 101 | [show](https://discord.com/channels/{guild_id}/{channel_id}/{skill.id}) 102 | ''' 103 | embed.add_field(name=name_link, value=description) 104 | return embed 105 | 106 | def paginated_list_messages(guild_id: int, channel_id: int, skills: Iterable, page_size: int = 25): 107 | paginated_skills = [skills[i:i + page_size] for i in range(0, len(skills), page_size)] 108 | num_pages = len(paginated_skills) 109 | return [list_message(guild_id, channel_id, paginated_skills[index], num_pages, index+1) for index in range(len(paginated_skills))] 110 | 111 | 112 | def people_subgraph_message(people_ids: Iterable[int]): 113 | mentions = ", ".join(f'<@!{pid}>' for pid in people_ids) 114 | embed = discord.Embed( 115 | title = k.MAP_MESSAGE_TITLE, 116 | colour = c.DISCORD_MESSAGE_COLOR, 117 | description = f''' 118 | It includes only a subset of {k.PEOPLE}: {mentions} 119 | (and the {k.ENTITIES_LONG} linked to them) 120 | ''' 121 | ) 122 | return embed 123 | 124 | def skills_subgraph_message(skills_terms: Iterable[str]): 125 | terms_string = ", ".join(f'"{term}"' for term in skills_terms) 126 | embed = discord.Embed( 127 | title = k.MAP_MESSAGE_TITLE, 128 | colour = c.DISCORD_MESSAGE_COLOR, 129 | description = f''' 130 | It includes only {k.ENTITY_SHORT} matching: {terms_string} 131 | ''' 132 | ) 133 | return embed 134 | 135 | def stats_message(people_count: int, skills_count: int, people_skills_count: int): 136 | embed = discord.Embed( 137 | title = k.STATS_MESSAGE_TITLE, 138 | colour = c.DISCORD_MESSAGE_COLOR, 139 | description = f''' 140 | 👤 {k.PEOPLE_UPPERCASE}: {people_count} 141 | {k.SKILL_ICON} {k.ENTITIES_LONG_UPPERCASE}: {skills_count} 142 | ↔️ Connections: {people_skills_count} 143 | ''' 144 | ) 145 | return embed 146 | -------------------------------------------------------------------------------- /constants/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | -------------------------------------------------------------------------------- /constants/core.py: -------------------------------------------------------------------------------- 1 | import discord 2 | 3 | # Hacky way to customize strings across multiple servers/channels/use-cases 4 | # Not pretty but works alright, and clean enough. 5 | class Cosmetics: 6 | # Note that custom fonts are not guaranteed to be installed on the system 7 | RENDER_CLOUD_FONT = "Source Serif Pro" 8 | RENDER_ELLIPSE_COLOR = "#B66AD6" 9 | RENDER_RECTANGLE_COLOR = "#FF3864" 10 | 11 | DISCORD_MESSAGE_COLOR = discord.Colour.magenta() 12 | 13 | class Strings: 14 | 15 | RG_WARRIOR_EMOJI_CODE = '<:warrior:720324879421014086>' 16 | RG_ALCHEMIST_EMOJI_CODE = '<:alchemist:750768541929832478>' 17 | RG_ARCHER_EMOJI_CODE = '<:archer:720324879215362109>' 18 | RG_MONK_EMOJI_CODE = '<:monk:756585425828839556>' 19 | RG_TAVERN_EMOJI_CODE = '<:tavern:750768608208224258>' 20 | 21 | JAPANESE_CASTLE_EMOJI_CODE = "\U0001F3EF" 22 | TRIANGLE_RULER_EMOJI_CODE = "\U0001F4D0" 23 | COLLISION_EMOJI_CODE = "\U0001F4A5" 24 | SCROLL_EMOJI_CODE = "\U0001F4DC" 25 | MECHANICAL_ARM_EMOJI_CODE = "\U0001F9BE" 26 | 27 | NEW_SKILL_REGEX = '^[\w][\w_]+$' 28 | EMOJI_REGEX = r'[^!\w\s,]' 29 | 30 | COMMAND_PREFIX = '!sb' 31 | SKILL_ICON = '⭐️' 32 | REACTION="✅" 33 | ACCEPTED_REACTIONS = [REACTION] 34 | 35 | DESCRIPTION = 'SkillBot helps you discover **skills** among people in your community 💕' 36 | PEOPLE = 'people' 37 | PEOPLE_UPPERCASE = 'People' 38 | ENTITIES_LONG = 'skills' 39 | ENTITIES_LONG_UPPERCASE = 'Skills' 40 | ENTITY_SHORT = "skill" 41 | HELLO_TITLE = 'Hello World!' 42 | PEOPLE = 'people' 43 | WORD_CLOUD_SIZE = 'number of people' 44 | ERROR_CONTACT_PERSON = '@mprime#9455' 45 | REACTION_REASON = 'have this skill' 46 | NEW_COMMAND_EXAMPLES = ["fishing", "dancing"] 47 | DRAW_SKILLS_COMMAND_NAME = 'skills' 48 | DRAW_SKILLS_COMMAND_EXAMPLES = ["hunt farm", "ui ux design"] 49 | 50 | STATS_MESSAGE_TITLE = "📊\nStatistics" 51 | MAP_MESSAGE_TITLE = "☝️\nHere's your map!" 52 | HELP_MESSAGE_TITLE = "❓\nHelp!" 53 | LIST_MESSAGE_TITLE = "List of skills:" 54 | INFO_MESSAGE_TITLE = "ℹ️\nInfo!" 55 | WORD_CLOUD_MESSAGE_TITLE = "☝️\nHere's your words cloud!" 56 | 57 | INFO_MESSAGE_DESCRIPTION = DESCRIPTION + ''' 58 | This bot is an experimental multiplayer community **game** to facilitate discovery of skills among community members. 59 | 60 | Source: 61 | ''' 62 | LIST_MESSAGE_DESCRIPTION = "Click on the **show** link to jump to the skill" 63 | 64 | COMMAND_ERROR_MESSAGE = "🤦‍♂️\nOoops!" 65 | DUPLICATE_SKILL_ERROR_MESSAGE = "Entry already exists!" 66 | DRAW_PEOPLE_ERROR_MESSAGE = 'Could not find the people. Make sure the people have an \'@\' before their handle!' 67 | 68 | # Calling this method is needed to define formatted strings 69 | def apply_generic_string_formatting(): 70 | Strings.DUPLICATE_SKILL_ERROR_MESSAGE = f''' 71 | The {Strings.ENTITY_SHORT} already exists! 72 | You should react with {Strings.REACTION} to the original message: 73 | https://discord.com/channels/{{}}/{{}}/{{}} 74 | ''' 75 | Strings.LIST_MESSAGE_TITLE = f'List of {Strings.ENTITIES_LONG} (page {{}} / {{}})' 76 | Strings.LIST_MESSAGE_DESCRIPTION = f"Click on the **show** link to jump to the {Strings.ENTITY_SHORT}" 77 | 78 | # Light metagame customization. 79 | # Expand to interests and hobbies in addition to skills 80 | # Add more octopi 81 | def apply_metagame_customization(): 82 | print(f'Applying customization: 🐙 Metagame') 83 | Strings.COMMAND_PREFIX = '!sb' 84 | Strings.DESCRIPTION = 'SkillBot helps you discover common **interests**, **skills**, **hobbies** among players 🐙' 85 | Strings.SKILL_ICON = '🐙' 86 | Strings.REACTION="✅" 87 | Strings.ACCEPTED_REACTIONS = ["✅"] 88 | Strings.PEOPLE = 'players' 89 | Strings.PEOPLE_UPPERCASE = 'Players' 90 | Strings.ENTITIES_LONG = 'skills, interests, hobbies' 91 | Strings.ENTITIES_LONG_UPPERCASE = 'Skills, Interests, Hobbies' 92 | Strings.ENTITY_SHORT = "skill/interest/hobbies" 93 | Strings.HELLO_TITLE = '🐙 Hello MetaGame! 🐙' 94 | Strings.INFO_MESSAGE_DESCRIPTION = Strings.DESCRIPTION + f''' 95 | This bot is an experimental multiplayer **game** to facilitate discovery of common interests among players. 96 | 97 | Suggestions? Bugs? Wanna help? 98 | Ping {Strings.ERROR_CONTACT_PERSON}. 99 | 100 | Source: 101 | ''' 102 | Strings.PEOPLE = 'players' 103 | Strings.WORD_CLOUD_SIZE = 'number of players' 104 | Strings.REACTION_REASON = 'have this skill/interest/hobby' 105 | Strings.NEW_COMMAND_EXAMPLES = ["NFTs", "Python", "debating"] 106 | Strings.NEW_SKILL_REGEX = '^[\w][\w_]+$' 107 | Strings.DRAW_SKILLS_COMMAND_NAME = 'skills' 108 | Strings.DRAW_SKILLS_COMMAND_EXAMPLES = ["nft web3", "cooking"] 109 | Strings.DRAW_PEOPLE_ERROR_MESSAGE = f'Could not find the {Strings.PEOPLE}. Make sure the {Strings.PEOPLE} have an \'@\' before their handle!' 110 | 111 | # Light RaidGuild customization 112 | def apply_raidguild_customization(): 113 | print(f'Applying customization: ⚔️ RaidGuild') 114 | Strings.ERROR_CONTACT_PERSON = '@mprime#9455, @flip#3394, @govinda#3746' 115 | Strings.COMMAND_PREFIX = '!qm' 116 | Strings.DESCRIPTION = f''' 117 | After countless raids and demons slain, the Guild has appointed me as the Quartermaster. 118 | 119 | My is to job help you find fellow Guild members based on their skills and abilities. 120 | {Strings.RG_WARRIOR_EMOJI_CODE} {Strings.RG_ALCHEMIST_EMOJI_CODE} {Strings.RG_ARCHER_EMOJI_CODE} {Strings.RG_MONK_EMOJI_CODE} {Strings.RG_TAVERN_EMOJI_CODE} 121 | ''' 122 | Strings.SKILL_ICON = '⚔️' 123 | Strings.REACTION="🙋" 124 | Strings.ACCEPTED_REACTIONS = ["✅", "🙋"] 125 | Strings.PEOPLE = 'raiders' 126 | Strings.PEOPLE_UPPERCASE = 'Raiders' 127 | Strings.ENTITIES_LONG = 'skills and abilities' 128 | Strings.ENTITIES_LONG_UPPERCASE = "Skills and Abilities" 129 | Strings.ENTITY_SHORT = 'skills/abilities' 130 | Strings.HELLO_TITLE = f'{Strings.RG_WARRIOR_EMOJI_CODE}** Good day adventurer! Welcome to the Skill Hall!** {Strings.JAPANESE_CASTLE_EMOJI_CODE}' 131 | Strings.INFO_MESSAGE_DESCRIPTION = f''' 132 | After countless raids and demons slain, the Guild has appointed me as the Quartermaster. 133 | 134 | My is to job help you find fellow Guild members based on their skills and abilities. 135 | {Strings.RG_WARRIOR_EMOJI_CODE} {Strings.RG_ALCHEMIST_EMOJI_CODE} {Strings.RG_ARCHER_EMOJI_CODE} {Strings.RG_MONK_EMOJI_CODE} {Strings.RG_TAVERN_EMOJI_CODE} 136 | 137 | Suggestions? Bugs? Wanna help? 138 | Ping {Strings.ERROR_CONTACT_PERSON} 139 | 140 | Source: 141 | ''' 142 | Strings.WORD_CLOUD_SIZE = 'number of raiders' 143 | Strings.REACTION_REASON = 'have this skill or ability' 144 | Strings.NEW_COMMAND_EXAMPLES = ["typescript", "solidity", "design"] 145 | Strings.NEW_SKILL_REGEX = '^[\w][\w_]+$' 146 | 147 | Strings.STATS_MESSAGE_TITLE = f"{Strings.MECHANICAL_ARM_EMOJI_CODE} \nSee how mighty our Guild is! Death to Moloch!" 148 | Strings.MAP_MESSAGE_TITLE = "⚔\nBehold the skills of our guild!" 149 | Strings.HELP_MESSAGE_TITLE = f"{Strings.SCROLL_EMOJI_CODE} \n How may I assist you, adventurer?" 150 | Strings.LIST_MESSAGE_TITLE = f'Arsenal of {Strings.ENTITIES_LONG} (page {{}} / {{}})' 151 | Strings.INFO_MESSAGE_TITLE = f"{Strings.SCROLL_EMOJI_CODE} \n Stay a while and listen..." 152 | Strings.WORD_CLOUD_MESSAGE_TITLE = f"Word size is proportional to number of raiders..." 153 | 154 | Strings.LIST_MESSAGE_DESCRIPTION = f"Click upon the **show** link to jump to the {Strings.ENTITY_SHORT}, adventurer!" 155 | 156 | Strings.DRAW_SKILLS_COMMAND_NAME = 'skills' 157 | Strings.DRAW_SKILLS_COMMAND_EXAMPLES = ["React", "IPFS", "solidity smart-contracts"] 158 | 159 | Strings.DUPLICATE_SKILL_ERROR_MESSAGE = f''' 160 | We already have these {Strings.ENTITY_SHORT} in our arsenal! 161 | 162 | Might I suggest reacting with {Strings.REACTION} to the original message: 163 | https://discord.com/channels/{{}}/{{}}/{{}} 164 | ''' 165 | Strings.DRAW_SKILLS_ERROR_MESSAGE = f'Could not find the {Strings.ENTITY_SHORT}.' 166 | Strings.DRAW_PEOPLE_ERROR_MESSAGE = f'Could not find the {Strings.PEOPLE}. Make sure the {Strings.PEOPLE} have an \'@\' before their handle!' 167 | Strings.COMMAND_ERROR_MESSAGE = f"{Strings.COLLISION_EMOJI_CODE} \nYou seem to be mistaken..." 168 | 169 | Cosmetics.COLOR = discord.Colour.magenta() 170 | -------------------------------------------------------------------------------- /examples/common/graph.css: -------------------------------------------------------------------------------- 1 | /* 2 | Person: #f853c5 #f896d9 3 | Skill: #8753f8 #b697f8 4 | Link: #8bf872 5 | */ 6 | 7 | line.edge { 8 | stroke: #8bf872; 9 | opacity: 60% 10 | } 11 | 12 | circle.person { 13 | stroke-width: 0.5; 14 | fill: #f853c5; 15 | stroke: #f896d9; 16 | } 17 | 18 | text.person-label { 19 | stroke: #f853c5; 20 | stroke-width: 0.5; 21 | fill: #f896d9; 22 | } 23 | 24 | circle.skill { 25 | stroke: #9a35ff; 26 | stroke-width: 0.5; 27 | fill: #b697f8; 28 | } 29 | 30 | text.skill-label { 31 | stroke: #b697f8; 32 | stroke-width: 0.5; 33 | fill: #8bb4e0; 34 | } 35 | 36 | svg { 37 | font-size: 180%; 38 | background-color: #D3D3D3 39 | } 40 | -------------------------------------------------------------------------------- /examples/common/graph.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // import "https://d3js.org/d3.v7.min.js" 4 | 5 | async function force_graph() { 6 | 7 | const personNodeSize = 10 8 | const skillNodeSize = 20 9 | const $ = { 10 | width: window.innerWidth - 100, 11 | height: window.innerHeight - 200, 12 | nodes: [], 13 | people: {}, 14 | skills: {}, 15 | links: [], 16 | } 17 | 18 | const svg = d3.select("#canvas") 19 | 20 | var peopleIndex = {} 21 | people.forEach((p) => { 22 | p.type = 'person' 23 | $.nodes.push(p) 24 | $.people[p.id] = p 25 | }) 26 | 27 | var skillsIndex = {} 28 | skills.forEach((s) => { 29 | s.type = 'skill' 30 | $.nodes.push(s) 31 | $.skills[s.id] = s 32 | }) 33 | 34 | people_skills.forEach((ps) => { 35 | 36 | var person = $.people[ps.person] 37 | var skill = $.skills[ps.skill] 38 | if (person && skill) { 39 | console.log("〈" + person.name + " ↔️ " + skill.name + "〉") 40 | ps.source = person 41 | ps.target = skill 42 | $.links.push(ps) 43 | } else { 44 | console.log("Broken link! " + ps.person + "(" + person + ") <-> " + ps.skill + "(" + skill + ")") 45 | } 46 | }) 47 | 48 | function drag(simulation) { 49 | function dragstarted(event) { 50 | if (!event.active) simulation.alphaTarget(0.15).restart(); 51 | event.subject.fx = event.subject.x; 52 | event.subject.fy = event.subject.y; 53 | } 54 | 55 | function dragged(event) { 56 | event.subject.fx = event.x; 57 | event.subject.fy = event.y; 58 | } 59 | 60 | function dragended(event) { 61 | if (!event.active) simulation.alphaTarget(alphaMin); 62 | event.subject.fx = null; 63 | event.subject.fy = null; 64 | } 65 | 66 | return d3.drag() 67 | .on("start", dragstarted) 68 | .on("drag", dragged) 69 | .on("end", dragended); 70 | } 71 | 72 | 73 | const linkForce = d3 74 | .forceLink() 75 | .id(link => link.id) 76 | .strength(link => 0.2) 77 | .links($.links) 78 | 79 | const simulation = d3.forceSimulation() 80 | .force('link', linkForce) 81 | .force('attraction', d3.forceManyBody().strength(-100)) 82 | .force('repulsion', d3.forceCollide(105)) 83 | .force('center', d3.forceCenter($.width / 2, $.height / 2)) 84 | 85 | simulation.stop() 86 | 87 | 88 | // simulation.stop() 89 | 90 | simulation.nodes($.nodes).on('tick', tick) 91 | 92 | function tick() { 93 | $.nodeElements 94 | .attr("cx", node => node.x) 95 | .attr("cy", node => node.y) 96 | $.textElements 97 | .attr("x", node => node.x) 98 | .attr("y", node => node.y) 99 | $.linkElements 100 | .attr('x1', link => link.source.x) 101 | .attr('y1', link => link.source.y) 102 | .attr('x2', link => link.target.x) 103 | .attr('y2', link => link.target.y) 104 | } 105 | 106 | svg 107 | .style("width", $.width + 'px') 108 | .style("height", $.height + 'px') 109 | 110 | $.linkElements = svg.append('g') 111 | .selectAll('line') 112 | .data($.links) 113 | .enter().append('line') 114 | .attr('class', "edge") 115 | 116 | $.nodeElements = svg.append('g') 117 | .selectAll('circle') 118 | .data($.nodes) 119 | .enter().append('circle') 120 | .attr('r', node => (node.type == "skill" ? skillNodeSize : personNodeSize )) 121 | .attr('class', node => "node " + node.type) 122 | .call(drag(simulation)) 123 | 124 | $.textElements = svg.append('g') 125 | .selectAll('text') 126 | .data($.nodes) 127 | .enter().append('text') 128 | .text(node => node.name) 129 | .attr('dx', node => (node.type == "skill" ? skillNodeSize : personNodeSize )) 130 | .attr('dy', node => (node.type == "skill" ? -skillNodeSize : -personNodeSize )) 131 | .attr('class', node => "label " + node.type + "-label") 132 | 133 | simulation.alpha(0.25) 134 | simulation.restart() 135 | 136 | d3.select('#d3-canary') 137 | .text("✅ "); 138 | 139 | 140 | 141 | } 142 | -------------------------------------------------------------------------------- /examples/common/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/examples/common/index.html -------------------------------------------------------------------------------- /examples/data/raid_guild-s0.js: -------------------------------------------------------------------------------- 1 | var people = [ 2 | {id: "p-129702656418643968", name: "Gianni"}, 3 | {id: "p-185307556032413697", name: "Sundeep Charan Ramkumar"}, 4 | {id: "p-208321561982271489", name: "penguin"}, 5 | {id: "p-222844791744888832", name: "scottrepreneur"}, 6 | {id: "p-262323994936475649", name: "UniverseCEO"}, 7 | {id: "p-286245796289052673", name: "NickBoyadjian"}, 8 | {id: "p-287768540847800320", name: "plor"}, 9 | {id: "p-303278071174266883", name: "Max"}, 10 | {id: "p-409930721059405835", name: "joshsdoug"}, 11 | {id: "p-433629219898130435", name: "Delleon"}, 12 | {id: "p-435541241795444737", name: "dekanbro"}, 13 | {id: "p-505789102222737420", name: "Saimano"}, 14 | {id: "p-523401372188540939", name: "markop"}, 15 | {id: "p-679156502220636200", name: "Bau"}, 16 | {id: "p-703688028639461386", name: "mprime"}, 17 | {id: "p-707877605025644586", name: "DAO Jones"}, 18 | {id: "p-715073251696705618", name: "dan13ram"}, 19 | {id: "p-775464119037067285", name: "DamagedGoods"}, 20 | {id: "p-795881317619335209", name: "CassStoughton"}, 21 | ] 22 | 23 | var skills = [ 24 | {id: "s-831556065052852224", name: "python"}, 25 | {id: "s-831556687391359016", name: "internet_protocols"}, 26 | {id: "s-831556710702907433", name: "operating_systems"}, 27 | {id: "s-831556826981597186", name: "p2p_protocols"}, 28 | {id: "s-831556866122448916", name: "security"}, 29 | {id: "s-831556922632699955", name: "consensus_protocols"}, 30 | {id: "s-831557017674973214", name: "dependable_systems"}, 31 | {id: "s-831557121643905025", name: "c_and_c_plus_plus"}, 32 | {id: "s-831557145907560458", name: "search_and_recommendations"}, 33 | {id: "s-831557254347096136", name: "devops"}, 34 | {id: "s-831557300409860106", name: "big_data_pipelines"}, 35 | {id: "s-831557343954206791", name: "cloud_infrastructure"}, 36 | {id: "s-831557469809541142", name: "groupware"}, 37 | {id: "s-831557520556032031", name: "web3"}, 38 | {id: "s-831561301938667520", name: "react"}, 39 | {id: "s-831561372491841577", name: "typescript"}, 40 | {id: "s-831561435104542760", name: "node_js"}, 41 | {id: "s-831565192282112046", name: "graphic_design"}, 42 | {id: "s-831565297998757998", name: "branding"}, 43 | {id: "s-832734043270283275", name: "sound_synthesis"}, 44 | {id: "s-833448406742597722", name: "scribe"}, 45 | {id: "s-833448906196254811", name: "bus_dev"}, 46 | {id: "s-833449137575821312", name: "product_design"}, 47 | {id: "s-833449267758104596", name: "usability_testing"}, 48 | {id: "s-834418212237344818", name: "video_production"}, 49 | {id: "s-834418427384299580", name: "teaching"}, 50 | {id: "s-834419497183871016", name: "audio_post_production"}, 51 | {id: "s-834419616415088670", name: "video_post_production"}, 52 | {id: "s-834420004031823892", name: "photography"}, 53 | {id: "s-835420490998611988", name: "user_experience"}, 54 | ] 55 | 56 | var people_skills = [ 57 | {person: "p-208321561982271489", skill: "s-831556065052852224"}, 58 | {person: "p-185307556032413697", skill: "s-831561372491841577"}, 59 | {person: "p-715073251696705618", skill: "s-831561372491841577"}, 60 | {person: "p-262323994936475649", skill: "s-833449267758104596"}, 61 | {person: "p-523401372188540939", skill: "s-831565297998757998"}, 62 | {person: "p-703688028639461386", skill: "s-831556826981597186"}, 63 | {person: "p-703688028639461386", skill: "s-831557017674973214"}, 64 | {person: "p-707877605025644586", skill: "s-834418427384299580"}, 65 | {person: "p-433629219898130435", skill: "s-831565192282112046"}, 66 | {person: "p-679156502220636200", skill: "s-831557520556032031"}, 67 | {person: "p-775464119037067285", skill: "s-834418427384299580"}, 68 | {person: "p-433629219898130435", skill: "s-833449267758104596"}, 69 | {person: "p-523401372188540939", skill: "s-835420490998611988"}, 70 | {person: "p-208321561982271489", skill: "s-833449137575821312"}, 71 | {person: "p-715073251696705618", skill: "s-831556710702907433"}, 72 | {person: "p-505789102222737420", skill: "s-831557520556032031"}, 73 | {person: "p-505789102222737420", skill: "s-831561435104542760"}, 74 | {person: "p-715073251696705618", skill: "s-831557121643905025"}, 75 | {person: "p-703688028639461386", skill: "s-834418427384299580"}, 76 | {person: "p-262323994936475649", skill: "s-831565297998757998"}, 77 | {person: "p-303278071174266883", skill: "s-833449137575821312"}, 78 | {person: "p-185307556032413697", skill: "s-831557520556032031"}, 79 | {person: "p-523401372188540939", skill: "s-831557520556032031"}, 80 | {person: "p-185307556032413697", skill: "s-831561435104542760"}, 81 | {person: "p-287768540847800320", skill: "s-831556710702907433"}, 82 | {person: "p-703688028639461386", skill: "s-831556687391359016"}, 83 | {person: "p-703688028639461386", skill: "s-831556866122448916"}, 84 | {person: "p-129702656418643968", skill: "s-831565192282112046"}, 85 | {person: "p-433629219898130435", skill: "s-831565297998757998"}, 86 | {person: "p-703688028639461386", skill: "s-831557254347096136"}, 87 | {person: "p-707877605025644586", skill: "s-834420004031823892"}, 88 | {person: "p-287768540847800320", skill: "s-831557520556032031"}, 89 | {person: "p-433629219898130435", skill: "s-833448906196254811"}, 90 | {person: "p-703688028639461386", skill: "s-831557469809541142"}, 91 | {person: "p-795881317619335209", skill: "s-831565192282112046"}, 92 | {person: "p-208321561982271489", skill: "s-833449267758104596"}, 93 | {person: "p-185307556032413697", skill: "s-831556065052852224"}, 94 | {person: "p-715073251696705618", skill: "s-831556065052852224"}, 95 | {person: "p-679156502220636200", skill: "s-833449137575821312"}, 96 | {person: "p-222844791744888832", skill: "s-831561372491841577"}, 97 | {person: "p-505789102222737420", skill: "s-831561301938667520"}, 98 | {person: "p-715073251696705618", skill: "s-831556922632699955"}, 99 | {person: "p-303278071174266883", skill: "s-831565192282112046"}, 100 | {person: "p-433629219898130435", skill: "s-831556710702907433"}, 101 | {person: "p-433629219898130435", skill: "s-831556922632699955"}, 102 | {person: "p-208321561982271489", skill: "s-831556866122448916"}, 103 | {person: "p-185307556032413697", skill: "s-831561301938667520"}, 104 | {person: "p-433629219898130435", skill: "s-831557145907560458"}, 105 | {person: "p-208321561982271489", skill: "s-831557121643905025"}, 106 | {person: "p-287768540847800320", skill: "s-831556826981597186"}, 107 | {person: "p-523401372188540939", skill: "s-831565192282112046"}, 108 | {person: "p-703688028639461386", skill: "s-831556710702907433"}, 109 | {person: "p-715073251696705618", skill: "s-831561435104542760"}, 110 | {person: "p-286245796289052673", skill: "s-831561301938667520"}, 111 | {person: "p-303278071174266883", skill: "s-834420004031823892"}, 112 | {person: "p-433629219898130435", skill: "s-831557520556032031"}, 113 | {person: "p-523401372188540939", skill: "s-833449137575821312"}, 114 | {person: "p-129702656418643968", skill: "s-831565297998757998"}, 115 | {person: "p-703688028639461386", skill: "s-831557121643905025"}, 116 | {person: "p-208321561982271489", skill: "s-831561372491841577"}, 117 | {person: "p-707877605025644586", skill: "s-834419497183871016"}, 118 | {person: "p-185307556032413697", skill: "s-834418427384299580"}, 119 | {person: "p-703688028639461386", skill: "s-831557300409860106"}, 120 | {person: "p-208321561982271489", skill: "s-831565297998757998"}, 121 | {person: "p-703688028639461386", skill: "s-831557520556032031"}, 122 | {person: "p-435541241795444737", skill: "s-831557520556032031"}, 123 | {person: "p-435541241795444737", skill: "s-831561435104542760"}, 124 | {person: "p-505789102222737420", skill: "s-831557254347096136"}, 125 | {person: "p-679156502220636200", skill: "s-833448406742597722"}, 126 | {person: "p-222844791744888832", skill: "s-831557520556032031"}, 127 | {person: "p-679156502220636200", skill: "s-833449267758104596"}, 128 | {person: "p-303278071174266883", skill: "s-831565297998757998"}, 129 | {person: "p-262323994936475649", skill: "s-831565192282112046"}, 130 | {person: "p-433629219898130435", skill: "s-831556826981597186"}, 131 | {person: "p-287768540847800320", skill: "s-831556687391359016"}, 132 | {person: "p-703688028639461386", skill: "s-831556065052852224"}, 133 | {person: "p-435541241795444737", skill: "s-831556065052852224"}, 134 | {person: "p-715073251696705618", skill: "s-831561301938667520"}, 135 | {person: "p-262323994936475649", skill: "s-833449137575821312"}, 136 | {person: "p-707877605025644586", skill: "s-832734043270283275"}, 137 | {person: "p-262323994936475649", skill: "s-834418427384299580"}, 138 | {person: "p-409930721059405835", skill: "s-833448906196254811"}, 139 | {person: "p-303278071174266883", skill: "s-835420490998611988"}, 140 | {person: "p-222844791744888832", skill: "s-831556065052852224"}, 141 | {person: "p-703688028639461386", skill: "s-831556922632699955"}, 142 | {person: "p-208321561982271489", skill: "s-831557520556032031"}, 143 | {person: "p-707877605025644586", skill: "s-834418212237344818"}, 144 | {person: "p-222844791744888832", skill: "s-831556826981597186"}, 145 | {person: "p-287768540847800320", skill: "s-831557254347096136"}, 146 | {person: "p-703688028639461386", skill: "s-831557145907560458"}, 147 | {person: "p-208321561982271489", skill: "s-831561435104542760"}, 148 | {person: "p-775464119037067285", skill: "s-833448906196254811"}, 149 | {person: "p-707877605025644586", skill: "s-834419616415088670"}, 150 | {person: "p-703688028639461386", skill: "s-831557343954206791"}, 151 | {person: "p-435541241795444737", skill: "s-831561301938667520"}, 152 | {person: "p-433629219898130435", skill: "s-833449137575821312"}, 153 | {person: "p-679156502220636200", skill: "s-831565297998757998"}, 154 | {person: "p-715073251696705618", skill: "s-831556687391359016"}, 155 | {person: "p-679156502220636200", skill: "s-833448906196254811"}, 156 | {person: "p-222844791744888832", skill: "s-831561301938667520"}, 157 | {person: "p-715073251696705618", skill: "s-831556866122448916"}, 158 | {person: "p-433629219898130435", skill: "s-831556687391359016"}, 159 | ] 160 | 161 | -------------------------------------------------------------------------------- /examples/data/raid_guild-s1.js: -------------------------------------------------------------------------------- 1 | var people = [ 2 | {id: "p-98242908246781952", name: "Neokry"}, 3 | {id: "p-131146551295868928", name: "nintynick"}, 4 | {id: "p-197852493537869824", name: "frogmonkee"}, 5 | {id: "p-218260793605488641", name: "reallyspicy"}, 6 | {id: "p-218896577912242179", name: "Alex Hidalgo | ⛈ Stormx"}, 7 | {id: "p-247134767189852162", name: "Beist"}, 8 | {id: "p-262709549889224704", name: "owenmurovec"}, 9 | {id: "p-284565381530779648", name: "izkp"}, 10 | {id: "p-294645166705344512", name: "adamtati"}, 11 | {id: "p-316004191774572574", name: "dwarrowly"}, 12 | {id: "p-328005404066054156", name: "sackofjoy"}, 13 | {id: "p-349279905457897473", name: "geo"}, 14 | {id: "p-362705284029743105", name: "charingane"}, 15 | {id: "p-381511403439259649", name: "adrienne"}, 16 | {id: "p-383882065164369923", name: "freiheit"}, 17 | {id: "p-388738579184091137", name: "AzfarEphr"}, 18 | {id: "p-405109821130539018", name: "tvrlol"}, 19 | {id: "p-435541241795444737", name: "dekanbro"}, 20 | {id: "p-443120198577291295", name: "tenfinney"}, 21 | {id: "p-446823062084780034", name: "annwillmott"}, 22 | {id: "p-468381615794421761", name: "hruday"}, 23 | {id: "p-493136233351086081", name: "jonathan_p"}, 24 | {id: "p-494507365333467147", name: "Keishi@hashmimic"}, 25 | {id: "p-555925976723226630", name: "mkdir"}, 26 | {id: "p-575968905198043146", name: "earth2travis"}, 27 | {id: "p-595703993565577229", name: "bitbeckers"}, 28 | {id: "p-618615769713868812", name: "billw"}, 29 | {id: "p-663732871441154088", name: "alexs"}, 30 | {id: "p-668037938495488000", name: "Echolinq"}, 31 | {id: "p-676458363911012363", name: "arentweall"}, 32 | {id: "p-679156502220636200", name: "Bau"}, 33 | {id: "p-681141240510545999", name: "petra_fran"}, 34 | {id: "p-703688028639461386", name: "mprime"}, 35 | {id: "p-708419197587292240", name: "Web3"}, 36 | {id: "p-738038918007488603", name: "minatofund"}, 37 | {id: "p-756623376713252886", name: "AmandaG"}, 38 | {id: "p-758837917677518879", name: "DrHongo"}, 39 | {id: "p-761861661224730624", name: "deeayeen"}, 40 | {id: "p-775464119037067285", name: "DamagedGoods"}, 41 | {id: "p-793834953095643156", name: "ngerald"}, 42 | {id: "p-796842908984279061", name: "ladylaurengrace"}, 43 | {id: "p-802748079404875796", name: "sulla.eth"}, 44 | {id: "p-803340399606497281", name: "mlouischarles"}, 45 | {id: "p-804510442063659069", name: "blckmamba"}, 46 | {id: "p-805936770910912564", name: "itsmils"}, 47 | {id: "p-808480462586249286", name: "Kortelin150mg"}, 48 | {id: "p-817190136416043038", name: "Brandon1"}, 49 | {id: "p-818702170793967646", name: "Tosca"}, 50 | {id: "p-822279508462796810", name: "miss_bayley"}, 51 | {id: "p-822915483484422165", name: "tenacious_bird_of_prey"}, 52 | {id: "p-824389474833924116", name: "RankoG33"}, 53 | {id: "p-827122722818752563", name: "Rwiju"}, 54 | ] 55 | 56 | var skills = [ 57 | {id: "s-843923446936371240", name: "python"}, 58 | {id: "s-843923583323340810", name: "event_production"}, 59 | {id: "s-843923636100530178", name: "distributed_systems"}, 60 | {id: "s-844289174916169749", name: "nodejs"}, 61 | {id: "s-844289209908854819", name: "serverless"}, 62 | {id: "s-844289434652639262", name: "react"}, 63 | {id: "s-844289501031301170", name: "styled_components"}, 64 | {id: "s-844289586971672657", name: "solidity"}, 65 | {id: "s-844291803619786762", name: "encryption_ecdsa"}, 66 | {id: "s-844293468838297640", name: "ux_design"}, 67 | {id: "s-844294155797790772", name: "project_management"}, 68 | {id: "s-844294607663529984", name: "content_management_ipfs"}, 69 | {id: "s-844294761430777906", name: "non_fungible_assets_nfts"}, 70 | {id: "s-844295275585208400", name: "kong"}, 71 | {id: "s-844295296142147658", name: "aws_cdk"}, 72 | {id: "s-844295330739912837", name: "fastify"}, 73 | {id: "s-844295393239236618", name: "sql"}, 74 | {id: "s-844295462856556564", name: "graphql"}, 75 | {id: "s-844295517525245953", name: "oracles_chainlink"}, 76 | {id: "s-844295977200123927", name: "technical_writing"}, 77 | {id: "s-844296083630325811", name: "general_interest_writing"}, 78 | {id: "s-844296196764074044", name: "meme_making"}, 79 | {id: "s-844296709629673503", name: "liquidity_pooling_defi"}, 80 | {id: "s-844297830666600467", name: "flow_charting_technical"}, 81 | {id: "s-844302552182554636", name: "typescript"}, 82 | {id: "s-844314125177061427", name: "design_sprints"}, 83 | {id: "s-844314660085956641", name: "scrum"}, 84 | {id: "s-844328749898137621", name: "coaching"}, 85 | {id: "s-844328798887346197", name: "facilitation"}, 86 | {id: "s-844328833964703774", name: "documentation"}, 87 | {id: "s-844329118043471932", name: "experimentation"}, 88 | {id: "s-844329190391545866", name: "process_improvement"}, 89 | {id: "s-844383773658447913", name: "ui_design"}, 90 | {id: "s-844383787337252894", name: "product_design"}, 91 | {id: "s-844397673694887958", name: "react_native"}, 92 | {id: "s-844412793612795905", name: "golang"}, 93 | {id: "s-844497357470695475", name: "rust"}, 94 | {id: "s-844571995232272435", name: "product_management"}, 95 | {id: "s-844602706324488233", name: "github_tutoring"}, 96 | {id: "s-844612210113118250", name: "branding"}, 97 | {id: "s-844616967562199082", name: "blockchain_basics"}, 98 | {id: "s-844618961856626739", name: "ux"}, 99 | {id: "s-844624321937801256", name: "databases_airtable_firebase"}, 100 | {id: "s-844668910372519937", name: "marketing"}, 101 | {id: "s-844741247749849099", name: "user_interviewing"}, 102 | {id: "s-844866979558326273", name: "ruby"}, 103 | {id: "s-844867011389685791", name: "elixir"}, 104 | {id: "s-844954122658250823", name: "nextjs"}, 105 | {id: "s-844987439147253761", name: "html"}, 106 | {id: "s-844995924169588767", name: "airtable"}, 107 | {id: "s-844996293008424990", name: "illustration"}, 108 | {id: "s-845016704677052417", name: "usability_testing"}, 109 | {id: "s-845016972730040320", name: "qualitative_research"}, 110 | {id: "s-845302208211451925", name: "javascript"}, 111 | {id: "s-845306342612926494", name: "angular"}, 112 | {id: "s-845306387448725514", name: "vue"}, 113 | {id: "s-845306672612376608", name: "nuxtjs"}, 114 | {id: "s-845306719197855807", name: "dart"}, 115 | {id: "s-845306746216906823", name: "flutter"}, 116 | {id: "s-845525750899015681", name: "max_msp"}, 117 | {id: "s-854107448787861515", name: "webflow"}, 118 | {id: "s-860808803679469588", name: "figma"}, 119 | ] 120 | 121 | var people_skills = [ 122 | {person: "p-822279508462796810", skill: "s-844741247749849099"}, 123 | {person: "p-681141240510545999", skill: "s-844328833964703774"}, 124 | {person: "p-328005404066054156", skill: "s-845302208211451925"}, 125 | {person: "p-493136233351086081", skill: "s-844314125177061427"}, 126 | {person: "p-294645166705344512", skill: "s-844294155797790772"}, 127 | {person: "p-284565381530779648", skill: "s-844297830666600467"}, 128 | {person: "p-98242908246781952", skill: "s-844295393239236618"}, 129 | {person: "p-681141240510545999", skill: "s-844289434652639262"}, 130 | {person: "p-468381615794421761", skill: "s-844497357470695475"}, 131 | {person: "p-758837917677518879", skill: "s-844314660085956641"}, 132 | {person: "p-328005404066054156", skill: "s-844412793612795905"}, 133 | {person: "p-676458363911012363", skill: "s-844328798887346197"}, 134 | {person: "p-818702170793967646", skill: "s-844383787337252894"}, 135 | {person: "p-218260793605488641", skill: "s-844571995232272435"}, 136 | {person: "p-284565381530779648", skill: "s-844289434652639262"}, 137 | {person: "p-676458363911012363", skill: "s-844329190391545866"}, 138 | {person: "p-758837917677518879", skill: "s-844289434652639262"}, 139 | {person: "p-316004191774572574", skill: "s-844296083630325811"}, 140 | {person: "p-247134767189852162", skill: "s-844314125177061427"}, 141 | {person: "p-197852493537869824", skill: "s-844295977200123927"}, 142 | {person: "p-676458363911012363", skill: "s-844294155797790772"}, 143 | {person: "p-247134767189852162", skill: "s-844329190391545866"}, 144 | {person: "p-131146551295868928", skill: "s-844383787337252894"}, 145 | {person: "p-284565381530779648", skill: "s-844954122658250823"}, 146 | {person: "p-822915483484422165", skill: "s-843923446936371240"}, 147 | {person: "p-663732871441154088", skill: "s-844329118043471932"}, 148 | {person: "p-575968905198043146", skill: "s-844328749898137621"}, 149 | {person: "p-805936770910912564", skill: "s-845016704677052417"}, 150 | {person: "p-758837917677518879", skill: "s-845302208211451925"}, 151 | {person: "p-796842908984279061", skill: "s-844296196764074044"}, 152 | {person: "p-575968905198043146", skill: "s-844289174916169749"}, 153 | {person: "p-494507365333467147", skill: "s-845306342612926494"}, 154 | {person: "p-381511403439259649", skill: "s-844571995232272435"}, 155 | {person: "p-494507365333467147", skill: "s-845306719197855807"}, 156 | {person: "p-362705284029743105", skill: "s-844996293008424990"}, 157 | {person: "p-328005404066054156", skill: "s-844289209908854819"}, 158 | {person: "p-197852493537869824", skill: "s-844668910372519937"}, 159 | {person: "p-247134767189852162", skill: "s-845016972730040320"}, 160 | {person: "p-802748079404875796", skill: "s-844296709629673503"}, 161 | {person: "p-443120198577291295", skill: "s-844616967562199082"}, 162 | {person: "p-575968905198043146", skill: "s-844987439147253761"}, 163 | {person: "p-775464119037067285", skill: "s-844297830666600467"}, 164 | {person: "p-681141240510545999", skill: "s-844296709629673503"}, 165 | {person: "p-131146551295868928", skill: "s-844328833964703774"}, 166 | {person: "p-668037938495488000", skill: "s-844295393239236618"}, 167 | {person: "p-796842908984279061", skill: "s-844612210113118250"}, 168 | {person: "p-468381615794421761", skill: "s-845302208211451925"}, 169 | {person: "p-468381615794421761", skill: "s-845306672612376608"}, 170 | {person: "p-802748079404875796", skill: "s-844289209908854819"}, 171 | {person: "p-98242908246781952", skill: "s-844294761430777906"}, 172 | {person: "p-663732871441154088", skill: "s-844383787337252894"}, 173 | {person: "p-294645166705344512", skill: "s-844293468838297640"}, 174 | {person: "p-818702170793967646", skill: "s-844996293008424990"}, 175 | {person: "p-405109821130539018", skill: "s-844612210113118250"}, 176 | {person: "p-805936770910912564", skill: "s-844296083630325811"}, 177 | {person: "p-808480462586249286", skill: "s-844294155797790772"}, 178 | {person: "p-793834953095643156", skill: "s-844329118043471932"}, 179 | {person: "p-493136233351086081", skill: "s-854107448787861515"}, 180 | {person: "p-494507365333467147", skill: "s-844314660085956641"}, 181 | {person: "p-446823062084780034", skill: "s-844296083630325811"}, 182 | {person: "p-381511403439259649", skill: "s-844293468838297640"}, 183 | {person: "p-668037938495488000", skill: "s-844616967562199082"}, 184 | {person: "p-131146551295868928", skill: "s-845302208211451925"}, 185 | {person: "p-595703993565577229", skill: "s-844289586971672657"}, 186 | {person: "p-758837917677518879", skill: "s-844289209908854819"}, 187 | {person: "p-822915483484422165", skill: "s-844296083630325811"}, 188 | {person: "p-493136233351086081", skill: "s-844624321937801256"}, 189 | {person: "p-443120198577291295", skill: "s-844293468838297640"}, 190 | {person: "p-822915483484422165", skill: "s-844297830666600467"}, 191 | {person: "p-383882065164369923", skill: "s-844741247749849099"}, 192 | {person: "p-443120198577291295", skill: "s-844294761430777906"}, 193 | {person: "p-575968905198043146", skill: "s-844295462856556564"}, 194 | {person: "p-294645166705344512", skill: "s-844383787337252894"}, 195 | {person: "p-793834953095643156", skill: "s-844987439147253761"}, 196 | {person: "p-805936770910912564", skill: "s-844618961856626739"}, 197 | {person: "p-663732871441154088", skill: "s-844314660085956641"}, 198 | {person: "p-805936770910912564", skill: "s-844741247749849099"}, 199 | {person: "p-405109821130539018", skill: "s-844383773658447913"}, 200 | {person: "p-218896577912242179", skill: "s-844294155797790772"}, 201 | {person: "p-328005404066054156", skill: "s-844302552182554636"}, 202 | {person: "p-494507365333467147", skill: "s-844954122658250823"}, 203 | {person: "p-663732871441154088", skill: "s-844289434652639262"}, 204 | {person: "p-804510442063659069", skill: "s-844328833964703774"}, 205 | {person: "p-494507365333467147", skill: "s-845302208211451925"}, 206 | {person: "p-468381615794421761", skill: "s-844289586971672657"}, 207 | {person: "p-822915483484422165", skill: "s-844618961856626739"}, 208 | {person: "p-328005404066054156", skill: "s-844289174916169749"}, 209 | {person: "p-493136233351086081", skill: "s-844383773658447913"}, 210 | {person: "p-595703993565577229", skill: "s-844397673694887958"}, 211 | {person: "p-443120198577291295", skill: "s-844383787337252894"}, 212 | {person: "p-218896577912242179", skill: "s-845016972730040320"}, 213 | {person: "p-575968905198043146", skill: "s-844602706324488233"}, 214 | {person: "p-98242908246781952", skill: "s-844314660085956641"}, 215 | {person: "p-575968905198043146", skill: "s-844618961856626739"}, 216 | {person: "p-98242908246781952", skill: "s-844328833964703774"}, 217 | {person: "p-822279508462796810", skill: "s-844383787337252894"}, 218 | {person: "p-575968905198043146", skill: "s-844741247749849099"}, 219 | {person: "p-468381615794421761", skill: "s-844995924169588767"}, 220 | {person: "p-468381615794421761", skill: "s-845016972730040320"}, 221 | {person: "p-131146551295868928", skill: "s-844328798887346197"}, 222 | {person: "p-802748079404875796", skill: "s-843923446936371240"}, 223 | {person: "p-761861661224730624", skill: "s-844289434652639262"}, 224 | {person: "p-468381615794421761", skill: "s-845306387448725514"}, 225 | {person: "p-98242908246781952", skill: "s-844289434652639262"}, 226 | {person: "p-328005404066054156", skill: "s-844987439147253761"}, 227 | {person: "p-818702170793967646", skill: "s-844668910372519937"}, 228 | {person: "p-708419197587292240", skill: "s-844616967562199082"}, 229 | {person: "p-284565381530779648", skill: "s-844295330739912837"}, 230 | {person: "p-197852493537869824", skill: "s-844328833964703774"}, 231 | {person: "p-805936770910912564", skill: "s-844295977200123927"}, 232 | {person: "p-595703993565577229", skill: "s-844302552182554636"}, 233 | {person: "p-98242908246781952", skill: "s-844954122658250823"}, 234 | {person: "p-676458363911012363", skill: "s-844296083630325811"}, 235 | {person: "p-775464119037067285", skill: "s-844571995232272435"}, 236 | {person: "p-676458363911012363", skill: "s-844297830666600467"}, 237 | {person: "p-494507365333467147", skill: "s-844296709629673503"}, 238 | {person: "p-443120198577291295", skill: "s-844328833964703774"}, 239 | {person: "p-703688028639461386", skill: "s-843923583323340810"}, 240 | {person: "p-595703993565577229", skill: "s-844289174916169749"}, 241 | {person: "p-443120198577291295", skill: "s-843923636100530178"}, 242 | {person: "p-131146551295868928", skill: "s-845016972730040320"}, 243 | {person: "p-822915483484422165", skill: "s-844295393239236618"}, 244 | {person: "p-595703993565577229", skill: "s-844289501031301170"}, 245 | {person: "p-383882065164369923", skill: "s-844571995232272435"}, 246 | {person: "p-443120198577291295", skill: "s-844289434652639262"}, 247 | {person: "p-822915483484422165", skill: "s-844295977200123927"}, 248 | {person: "p-383882065164369923", skill: "s-844616967562199082"}, 249 | {person: "p-822279508462796810", skill: "s-844383773658447913"}, 250 | {person: "p-793834953095643156", skill: "s-844618961856626739"}, 251 | {person: "p-443120198577291295", skill: "s-844291803619786762"}, 252 | {person: "p-663732871441154088", skill: "s-844295977200123927"}, 253 | {person: "p-805936770910912564", skill: "s-844571995232272435"}, 254 | {person: "p-575968905198043146", skill: "s-844295393239236618"}, 255 | {person: "p-756623376713252886", skill: "s-845016972730040320"}, 256 | {person: "p-381511403439259649", skill: "s-844996293008424990"}, 257 | {person: "p-468381615794421761", skill: "s-844302552182554636"}, 258 | {person: "p-676458363911012363", skill: "s-844618961856626739"}, 259 | {person: "p-328005404066054156", skill: "s-844295462856556564"}, 260 | {person: "p-676458363911012363", skill: "s-844741247749849099"}, 261 | {person: "p-494507365333467147", skill: "s-844668910372519937"}, 262 | {person: "p-446823062084780034", skill: "s-844571995232272435"}, 263 | {person: "p-817190136416043038", skill: "s-844328798887346197"}, 264 | {person: "p-817190136416043038", skill: "s-844329190391545866"}, 265 | {person: "p-468381615794421761", skill: "s-844289174916169749"}, 266 | {person: "p-247134767189852162", skill: "s-844618961856626739"}, 267 | {person: "p-575968905198043146", skill: "s-860808803679469588"}, 268 | {person: "p-822915483484422165", skill: "s-844616967562199082"}, 269 | {person: "p-796842908984279061", skill: "s-845016972730040320"}, 270 | {person: "p-493136233351086081", skill: "s-844328798887346197"}, 271 | {person: "p-804510442063659069", skill: "s-843923583323340810"}, 272 | {person: "p-802748079404875796", skill: "s-844295462856556564"}, 273 | {person: "p-218260793605488641", skill: "s-843923583323340810"}, 274 | {person: "p-817190136416043038", skill: "s-844294155797790772"}, 275 | {person: "p-738038918007488603", skill: "s-844295393239236618"}, 276 | {person: "p-218896577912242179", skill: "s-845016704677052417"}, 277 | {person: "p-575968905198043146", skill: "s-844571995232272435"}, 278 | {person: "p-668037938495488000", skill: "s-844289434652639262"}, 279 | {person: "p-493136233351086081", skill: "s-844289209908854819"}, 280 | {person: "p-663732871441154088", skill: "s-844867011389685791"}, 281 | {person: "p-328005404066054156", skill: "s-844602706324488233"}, 282 | {person: "p-808480462586249286", skill: "s-844296083630325811"}, 283 | {person: "p-681141240510545999", skill: "s-844295462856556564"}, 284 | {person: "p-468381615794421761", skill: "s-844987439147253761"}, 285 | {person: "p-493136233351086081", skill: "s-844294155797790772"}, 286 | {person: "p-818702170793967646", skill: "s-844612210113118250"}, 287 | {person: "p-218260793605488641", skill: "s-844668910372519937"}, 288 | {person: "p-827122722818752563", skill: "s-844289586971672657"}, 289 | {person: "p-284565381530779648", skill: "s-844294607663529984"}, 290 | {person: "p-98242908246781952", skill: "s-844289209908854819"}, 291 | {person: "p-284565381530779648", skill: "s-844295296142147658"}, 292 | {person: "p-758837917677518879", skill: "s-844294607663529984"}, 293 | {person: "p-493136233351086081", skill: "s-844995924169588767"}, 294 | {person: "p-618615769713868812", skill: "s-844289586971672657"}, 295 | {person: "p-758837917677518879", skill: "s-844295462856556564"}, 296 | {person: "p-676458363911012363", skill: "s-844295393239236618"}, 297 | {person: "p-443120198577291295", skill: "s-844296709629673503"}, 298 | {person: "p-446823062084780034", skill: "s-844294761430777906"}, 299 | {person: "p-443120198577291295", skill: "s-844314125177061427"}, 300 | {person: "p-247134767189852162", skill: "s-844294155797790772"}, 301 | {person: "p-575968905198043146", skill: "s-844329118043471932"}, 302 | {person: "p-822915483484422165", skill: "s-844293468838297640"}, 303 | {person: "p-703688028639461386", skill: "s-843923446936371240"}, 304 | {person: "p-822915483484422165", skill: "s-844294761430777906"}, 305 | {person: "p-383882065164369923", skill: "s-844383787337252894"}, 306 | {person: "p-822279508462796810", skill: "s-844314125177061427"}, 307 | {person: "p-443120198577291295", skill: "s-843923583323340810"}, 308 | {person: "p-218896577912242179", skill: "s-844296083630325811"}, 309 | {person: "p-575968905198043146", skill: "s-844289501031301170"}, 310 | {person: "p-793834953095643156", skill: "s-844571995232272435"}, 311 | {person: "p-443120198577291295", skill: "s-844289209908854819"}, 312 | {person: "p-575968905198043146", skill: "s-844293468838297640"}, 313 | {person: "p-793834953095643156", skill: "s-844616967562199082"}, 314 | {person: "p-218896577912242179", skill: "s-844314660085956641"}, 315 | {person: "p-468381615794421761", skill: "s-844295462856556564"}, 316 | {person: "p-676458363911012363", skill: "s-844571995232272435"}, 317 | {person: "p-328005404066054156", skill: "s-844295275585208400"}, 318 | {person: "p-756623376713252886", skill: "s-845016704677052417"}, 319 | {person: "p-676458363911012363", skill: "s-844616967562199082"}, 320 | {person: "p-328005404066054156", skill: "s-844295393239236618"}, 321 | {person: "p-575968905198043146", skill: "s-845016704677052417"}, 322 | {person: "p-775464119037067285", skill: "s-844328833964703774"}, 323 | {person: "p-822915483484422165", skill: "s-844383787337252894"}, 324 | {person: "p-247134767189852162", skill: "s-844571995232272435"}, 325 | {person: "p-802748079404875796", skill: "s-844289586971672657"}, 326 | {person: "p-796842908984279061", skill: "s-844987439147253761"}, 327 | {person: "p-493136233351086081", skill: "s-844302552182554636"}, 328 | {person: "p-493136233351086081", skill: "s-844328749898137621"}, 329 | {person: "p-294645166705344512", skill: "s-844294761430777906"}, 330 | {person: "p-575968905198043146", skill: "s-844383787337252894"}, 331 | {person: "p-681141240510545999", skill: "s-844294155797790772"}, 332 | {person: "p-493136233351086081", skill: "s-844289174916169749"}, 333 | {person: "p-131146551295868928", skill: "s-844296083630325811"}, 334 | {person: "p-493136233351086081", skill: "s-844289501031301170"}, 335 | {person: "p-284565381530779648", skill: "s-844289209908854819"}, 336 | {person: "p-676458363911012363", skill: "s-844329118043471932"}, 337 | {person: "p-218260793605488641", skill: "s-844612210113118250"}, 338 | {person: "p-284565381530779648", skill: "s-844289586971672657"}, 339 | {person: "p-494507365333467147", skill: "s-844383773658447913"}, 340 | {person: "p-758837917677518879", skill: "s-844289586971672657"}, 341 | {person: "p-756623376713252886", skill: "s-844296083630325811"}, 342 | {person: "p-676458363911012363", skill: "s-844293468838297640"}, 343 | {person: "p-493136233351086081", skill: "s-844987439147253761"}, 344 | {person: "p-676458363911012363", skill: "s-844294761430777906"}, 345 | {person: "p-494507365333467147", skill: "s-844294607663529984"}, 346 | {person: "p-443120198577291295", skill: "s-844295517525245953"}, 347 | {person: "p-98242908246781952", skill: "s-844624321937801256"}, 348 | {person: "p-316004191774572574", skill: "s-843923583323340810"}, 349 | {person: "p-575968905198043146", skill: "s-844314660085956641"}, 350 | {person: "p-681141240510545999", skill: "s-844397673694887958"}, 351 | {person: "p-247134767189852162", skill: "s-844293468838297640"}, 352 | {person: "p-575968905198043146", skill: "s-844328833964703774"}, 353 | {person: "p-381511403439259649", skill: "s-844383787337252894"}, 354 | {person: "p-793834953095643156", skill: "s-844383787337252894"}, 355 | {person: "p-218896577912242179", skill: "s-844295977200123927"}, 356 | {person: "p-494507365333467147", skill: "s-845306672612376608"}, 357 | {person: "p-756623376713252886", skill: "s-844618961856626739"}, 358 | {person: "p-824389474833924116", skill: "s-844668910372519937"}, 359 | {person: "p-756623376713252886", skill: "s-844741247749849099"}, 360 | {person: "p-247134767189852162", skill: "s-845016704677052417"}, 361 | {person: "p-468381615794421761", skill: "s-844295393239236618"}, 362 | {person: "p-676458363911012363", skill: "s-844383787337252894"}, 363 | {person: "p-595703993565577229", skill: "s-844616967562199082"}, 364 | {person: "p-443120198577291295", skill: "s-844612210113118250"}, 365 | {person: "p-494507365333467147", skill: "s-844412793612795905"}, 366 | {person: "p-443120198577291295", skill: "s-844624321937801256"}, 367 | {person: "p-802748079404875796", skill: "s-844329118043471932"}, 368 | {person: "p-681141240510545999", skill: "s-844302552182554636"}, 369 | {person: "p-796842908984279061", skill: "s-844618961856626739"}, 370 | {person: "p-493136233351086081", skill: "s-844295462856556564"}, 371 | {person: "p-493136233351086081", skill: "s-844296083630325811"}, 372 | {person: "p-349279905457897473", skill: "s-844618961856626739"}, 373 | {person: "p-98242908246781952", skill: "s-844294607663529984"}, 374 | {person: "p-679156502220636200", skill: "s-844616967562199082"}, 375 | {person: "p-681141240510545999", skill: "s-844289174916169749"}, 376 | {person: "p-131146551295868928", skill: "s-844294155797790772"}, 377 | {person: "p-284565381530779648", skill: "s-844302552182554636"}, 378 | {person: "p-793834953095643156", skill: "s-844314660085956641"}, 379 | {person: "p-468381615794421761", skill: "s-844397673694887958"}, 380 | {person: "p-681141240510545999", skill: "s-844289501031301170"}, 381 | {person: "p-381511403439259649", skill: "s-844383773658447913"}, 382 | {person: "p-808480462586249286", skill: "s-844294761430777906"}, 383 | {person: "p-676458363911012363", skill: "s-844314660085956641"}, 384 | {person: "p-817190136416043038", skill: "s-844741247749849099"}, 385 | {person: "p-284565381530779648", skill: "s-844289174916169749"}, 386 | {person: "p-443120198577291295", skill: "s-844383773658447913"}, 387 | {person: "p-676458363911012363", skill: "s-844328833964703774"}, 388 | {person: "p-758837917677518879", skill: "s-843923446936371240"}, 389 | {person: "p-805936770910912564", skill: "s-843923583323340810"}, 390 | {person: "p-758837917677518879", skill: "s-844289174916169749"}, 391 | {person: "p-676458363911012363", skill: "s-844289434652639262"}, 392 | {person: "p-247134767189852162", skill: "s-844314660085956641"}, 393 | {person: "p-197852493537869824", skill: "s-844296083630325811"}, 394 | {person: "p-494507365333467147", skill: "s-844289209908854819"}, 395 | {person: "p-443120198577291295", skill: "s-844294607663529984"}, 396 | {person: "p-247134767189852162", skill: "s-844328833964703774"}, 397 | {person: "p-494507365333467147", skill: "s-844289586971672657"}, 398 | {person: "p-446823062084780034", skill: "s-843923583323340810"}, 399 | {person: "p-805936770910912564", skill: "s-844668910372519937"}, 400 | {person: "p-663732871441154088", skill: "s-844328798887346197"}, 401 | {person: "p-131146551295868928", skill: "s-844571995232272435"}, 402 | {person: "p-822915483484422165", skill: "s-843923583323340810"}, 403 | {person: "p-575968905198043146", skill: "s-844314125177061427"}, 404 | {person: "p-663732871441154088", skill: "s-844329190391545866"}, 405 | {person: "p-446823062084780034", skill: "s-844616967562199082"}, 406 | {person: "p-328005404066054156", skill: "s-844328833964703774"}, 407 | {person: "p-663732871441154088", skill: "s-844289586971672657"}, 408 | {person: "p-468381615794421761", skill: "s-844289501031301170"}, 409 | {person: "p-247134767189852162", skill: "s-844741247749849099"}, 410 | {person: "p-663732871441154088", skill: "s-844294155797790772"}, 411 | {person: "p-494507365333467147", skill: "s-845306387448725514"}, 412 | {person: "p-262709549889224704", skill: "s-844397673694887958"}, 413 | {person: "p-328005404066054156", skill: "s-843923636100530178"}, 414 | {person: "p-328005404066054156", skill: "s-844289434652639262"}, 415 | {person: "p-708419197587292240", skill: "s-843923446936371240"}, 416 | {person: "p-575968905198043146", skill: "s-844616967562199082"}, 417 | {person: "p-804510442063659069", skill: "s-844294155797790772"}, 418 | {person: "p-443120198577291295", skill: "s-844602706324488233"}, 419 | {person: "p-802748079404875796", skill: "s-844314660085956641"}, 420 | {person: "p-294645166705344512", skill: "s-844314125177061427"}, 421 | {person: "p-681141240510545999", skill: "s-844296083630325811"}, 422 | {person: "p-131146551295868928", skill: "s-844328749898137621"}, 423 | {person: "p-468381615794421761", skill: "s-845016704677052417"}, 424 | {person: "p-349279905457897473", skill: "s-844397673694887958"}, 425 | {person: "p-468381615794421761", skill: "s-845306342612926494"}, 426 | {person: "p-131146551295868928", skill: "s-844329118043471932"}, 427 | {person: "p-796842908984279061", skill: "s-844616967562199082"}, 428 | {person: "p-328005404066054156", skill: "s-844954122658250823"}, 429 | {person: "p-802748079404875796", skill: "s-843923636100530178"}, 430 | {person: "p-349279905457897473", skill: "s-844571995232272435"}, 431 | {person: "p-98242908246781952", skill: "s-844289586971672657"}, 432 | {person: "p-796842908984279061", skill: "s-844668910372519937"}, 433 | {person: "p-738038918007488603", skill: "s-844289209908854819"}, 434 | {person: "p-595703993565577229", skill: "s-844314660085956641"}, 435 | {person: "p-676458363911012363", skill: "s-844314125177061427"}, 436 | {person: "p-494507365333467147", skill: "s-844302552182554636"}, 437 | {person: "p-262709549889224704", skill: "s-844289174916169749"}, 438 | {person: "p-681141240510545999", skill: "s-844618961856626739"}, 439 | {person: "p-803340399606497281", skill: "s-844294155797790772"}, 440 | {person: "p-756623376713252886", skill: "s-844293468838297640"}, 441 | {person: "p-262709549889224704", skill: "s-844289501031301170"}, 442 | {person: "p-595703993565577229", skill: "s-844289434652639262"}, 443 | {person: "p-443120198577291295", skill: "s-844289586971672657"}, 444 | {person: "p-822915483484422165", skill: "s-844296196764074044"}, 445 | {person: "p-494507365333467147", skill: "s-844289174916169749"}, 446 | {person: "p-494507365333467147", skill: "s-844289501031301170"}, 447 | {person: "p-827122722818752563", skill: "s-844616967562199082"}, 448 | {person: "p-805936770910912564", skill: "s-844612210113118250"}, 449 | {person: "p-349279905457897473", skill: "s-844289174916169749"}, 450 | {person: "p-676458363911012363", skill: "s-844668910372519937"}, 451 | {person: "p-618615769713868812", skill: "s-844571995232272435"}, 452 | {person: "p-328005404066054156", skill: "s-844295977200123927"}, 453 | {person: "p-349279905457897473", skill: "s-844293468838297640"}, 454 | {person: "p-218260793605488641", skill: "s-844296196764074044"}, 455 | {person: "p-595703993565577229", skill: "s-845302208211451925"}, 456 | {person: "p-756623376713252886", skill: "s-844383787337252894"}, 457 | {person: "p-405109821130539018", skill: "s-844293468838297640"}, 458 | {person: "p-247134767189852162", skill: "s-844616967562199082"}, 459 | {person: "p-468381615794421761", skill: "s-844289434652639262"}, 460 | {person: "p-247134767189852162", skill: "s-844668910372519937"}, 461 | {person: "p-668037938495488000", skill: "s-844329190391545866"}, 462 | {person: "p-802748079404875796", skill: "s-844295393239236618"}, 463 | {person: "p-802748079404875796", skill: "s-844295977200123927"}, 464 | {person: "p-98242908246781952", skill: "s-844302552182554636"}, 465 | {person: "p-663732871441154088", skill: "s-844866979558326273"}, 466 | {person: "p-668037938495488000", skill: "s-844289586971672657"}, 467 | {person: "p-668037938495488000", skill: "s-844294155797790772"}, 468 | {person: "p-493136233351086081", skill: "s-844293468838297640"}, 469 | {person: "p-468381615794421761", skill: "s-844996293008424990"}, 470 | {person: "p-131146551295868928", skill: "s-844314660085956641"}, 471 | {person: "p-708419197587292240", skill: "s-844412793612795905"}, 472 | {person: "p-98242908246781952", skill: "s-844289174916169749"}, 473 | {person: "p-388738579184091137", skill: "s-843923583323340810"}, 474 | {person: "p-738038918007488603", skill: "s-843923446936371240"}, 475 | {person: "p-98242908246781952", skill: "s-844289501031301170"}, 476 | {person: "p-284565381530779648", skill: "s-844295393239236618"}, 477 | {person: "p-805936770910912564", skill: "s-844294607663529984"}, 478 | {person: "p-131146551295868928", skill: "s-844289434652639262"}, 479 | {person: "p-756623376713252886", skill: "s-844383773658447913"}, 480 | {person: "p-98242908246781952", skill: "s-844987439147253761"}, 481 | {person: "p-131146551295868928", skill: "s-844741247749849099"}, 482 | {person: "p-247134767189852162", skill: "s-844294761430777906"}, 483 | {person: "p-575968905198043146", skill: "s-844383773658447913"}, 484 | {person: "p-808480462586249286", skill: "s-844668910372519937"}, 485 | {person: "p-822915483484422165", skill: "s-844294607663529984"}, 486 | {person: "p-703688028639461386", skill: "s-843923636100530178"}, 487 | {person: "p-493136233351086081", skill: "s-844383787337252894"}, 488 | {person: "p-262709549889224704", skill: "s-844289434652639262"}, 489 | {person: "p-443120198577291295", skill: "s-844289174916169749"}, 490 | {person: "p-443120198577291295", skill: "s-844289501031301170"}, 491 | {person: "p-349279905457897473", skill: "s-844314660085956641"}, 492 | {person: "p-218896577912242179", skill: "s-844314125177061427"}, 493 | {person: "p-663732871441154088", skill: "s-844295462856556564"}, 494 | {person: "p-758837917677518879", skill: "s-844616967562199082"}, 495 | {person: "p-618615769713868812", skill: "s-844383787337252894"}, 496 | {person: "p-328005404066054156", skill: "s-844295330739912837"}, 497 | {person: "p-676458363911012363", skill: "s-844612210113118250"}, 498 | {person: "p-555925976723226630", skill: "s-843923446936371240"}, 499 | {person: "p-349279905457897473", skill: "s-844289434652639262"}, 500 | {person: "p-494507365333467147", skill: "s-844618961856626739"}, 501 | {person: "p-443120198577291295", skill: "s-844987439147253761"}, 502 | {person: "p-218260793605488641", skill: "s-844296083630325811"}, 503 | {person: "p-817190136416043038", skill: "s-844328833964703774"}, 504 | {person: "p-775464119037067285", skill: "s-844329190391545866"}, 505 | {person: "p-349279905457897473", skill: "s-844741247749849099"}, 506 | {person: "p-796842908984279061", skill: "s-844996293008424990"}, 507 | {person: "p-679156502220636200", skill: "s-844668910372519937"}, 508 | {person: "p-98242908246781952", skill: "s-844295462856556564"}, 509 | {person: "p-493136233351086081", skill: "s-844328833964703774"}, 510 | {person: "p-284565381530779648", skill: "s-844329118043471932"}, 511 | {person: "p-805936770910912564", skill: "s-844329190391545866"}, 512 | {person: "p-676458363911012363", skill: "s-844383773658447913"}, 513 | {person: "p-383882065164369923", skill: "s-844294155797790772"}, 514 | {person: "p-793834953095643156", skill: "s-844294607663529984"}, 515 | {person: "p-446823062084780034", skill: "s-844328798887346197"}, 516 | {person: "p-284565381530779648", skill: "s-844294761430777906"}, 517 | {person: "p-316004191774572574", skill: "s-844296196764074044"}, 518 | {person: "p-805936770910912564", skill: "s-844294155797790772"}, 519 | {person: "p-493136233351086081", skill: "s-844954122658250823"}, 520 | {person: "p-758837917677518879", skill: "s-844294761430777906"}, 521 | {person: "p-802748079404875796", skill: "s-844497357470695475"}, 522 | {person: "p-383882065164369923", skill: "s-845016972730040320"}, 523 | {person: "p-494507365333467147", skill: "s-844295393239236618"}, 524 | {person: "p-446823062084780034", skill: "s-844294155797790772"}, 525 | {person: "p-443120198577291295", skill: "s-844297830666600467"}, 526 | {person: "p-756623376713252886", skill: "s-843923583323340810"}, 527 | {person: "p-575968905198043146", skill: "s-844328798887346197"}, 528 | {person: "p-808480462586249286", skill: "s-844612210113118250"}, 529 | {person: "p-805936770910912564", skill: "s-845016972730040320"}, 530 | {person: "p-575968905198043146", skill: "s-844329190391545866"}, 531 | {person: "p-822915483484422165", skill: "s-844294155797790772"}, 532 | {person: "p-575968905198043146", skill: "s-844289209908854819"}, 533 | {person: "p-494507365333467147", skill: "s-845306746216906823"}, 534 | {person: "p-575968905198043146", skill: "s-844294155797790772"}, 535 | {person: "p-381511403439259649", skill: "s-844618961856626739"}, 536 | {person: "p-796842908984279061", skill: "s-843923583323340810"}, 537 | {person: "p-405109821130539018", skill: "s-844314125177061427"}, 538 | {person: "p-708419197587292240", skill: "s-844289174916169749"}, 539 | {person: "p-328005404066054156", skill: "s-844295296142147658"}, 540 | {person: "p-802748079404875796", skill: "s-844328833964703774"}, 541 | {person: "p-494507365333467147", skill: "s-844616967562199082"}, 542 | {person: "p-822279508462796810", skill: "s-844618961856626739"}, 543 | {person: "p-575968905198043146", skill: "s-845016972730040320"}, 544 | {person: "p-681141240510545999", skill: "s-844314660085956641"}, 545 | {person: "p-493136233351086081", skill: "s-844295977200123927"}, 546 | ] 547 | 548 | -------------------------------------------------------------------------------- /examples/data/raid_guild-s2.js: -------------------------------------------------------------------------------- 1 | var people = [ 2 | {id: "p-125227954098667520", name: "musnit"}, 3 | {id: "p-176141900229771265", name: "thebeyondr"}, 4 | {id: "p-176162785460224002", name: "nitegeist"}, 5 | {id: "p-230346367690604544", name: "0xJojo"}, 6 | {id: "p-253511881438920704", name: "Liquidiot"}, 7 | {id: "p-266231791369715713", name: "koloz"}, 8 | {id: "p-275681329155407873", name: "ffstrauf"}, 9 | {id: "p-278739943524925440", name: "azng | Bitski"}, 10 | {id: "p-281946480787324938", name: "BruceDev"}, 11 | {id: "p-282788499701104640", name: "tn"}, 12 | {id: "p-288019500132073472", name: "divine_comedian"}, 13 | {id: "p-299856078894661633", name: "markus"}, 14 | {id: "p-328657481481977871", name: "BarisAydek"}, 15 | {id: "p-359701069946814487", name: "prsahu"}, 16 | {id: "p-374606417488904192", name: "wolffan"}, 17 | {id: "p-384516597475180545", name: "RumpBumper"}, 18 | {id: "p-388679223302291458", name: "Kag"}, 19 | {id: "p-393498292308410411", name: "gnarlysagan"}, 20 | {id: "p-398927951653109780", name: "ChaseM"}, 21 | {id: "p-403619359869894656", name: "solstice.sebastian"}, 22 | {id: "p-405561963037327361", name: "theSociableMe"}, 23 | {id: "p-426479599338127390", name: "Jeremy 💧"}, 24 | {id: "p-429611433685417986", name: "mheuer"}, 25 | {id: "p-564070757856968704", name: "mindsgn"}, 26 | {id: "p-571699490357116940", name: "kevinyc"}, 27 | {id: "p-578349648985718806", name: "Raoul Duke"}, 28 | {id: "p-587937947756789760", name: "sriram"}, 29 | {id: "p-691275910808338444", name: "TravisWyche"}, 30 | {id: "p-703688028639461386", name: "mprime"}, 31 | {id: "p-707877605025644586", name: "DAO Jones"}, 32 | {id: "p-719003771027324939", name: "iluiscastillo"}, 33 | {id: "p-732794021801492490", name: "ETHChainHeavy"}, 34 | {id: "p-758329214834311251", name: "jengajojo"}, 35 | {id: "p-776482013162111028", name: "harshmaur"}, 36 | {id: "p-786217377447804988", name: "Chiali"}, 37 | {id: "p-795751965857677312", name: "xivanc01"}, 38 | {id: "p-801539080801222727", name: "0xMasayoshi"}, 39 | {id: "p-803455455912525834", name: "CPTNskeletor"}, 40 | {id: "p-820300955286241290", name: "jordanlovesred"}, 41 | {id: "p-848987946266525706", name: "willdrogriff"}, 42 | ] 43 | 44 | var skills = [ 45 | {id: "s-876581941082730518", name: "python"}, 46 | {id: "s-876581990994952242", name: "discord_bot"}, 47 | {id: "s-876867434827624469", name: "swift"}, 48 | {id: "s-876867528226385930", name: "react_native"}, 49 | {id: "s-876867628910653450", name: "ruby_on_rails"}, 50 | {id: "s-876867646447050814", name: "java"}, 51 | {id: "s-876868010466488400", name: "economy"}, 52 | {id: "s-876868124379603024", name: "react"}, 53 | {id: "s-876868259629113414", name: "bash"}, 54 | {id: "s-876868335298551828", name: "management"}, 55 | {id: "s-876871217536200747", name: "solidity"}, 56 | {id: "s-876871335232557106", name: "node_js"}, 57 | {id: "s-876871385442574416", name: "typescript"}, 58 | {id: "s-876871487691317318", name: "css"}, 59 | {id: "s-876871774162260048", name: "mocha"}, 60 | {id: "s-876871844110692413", name: "sql"}, 61 | {id: "s-876871862863421543", name: "nosql"}, 62 | {id: "s-876910812915179570", name: "graphql"}, 63 | {id: "s-876910949934706728", name: "ethersjs"}, 64 | {id: "s-876910982457331783", name: "hardhat"}, 65 | {id: "s-876967169836060682", name: "javascript"}, 66 | {id: "s-877010750030372864", name: "visual_interaction_design"}, 67 | {id: "s-877011238104744028", name: "data_analysis"}, 68 | {id: "s-877011383974244413", name: "machine_learning"}, 69 | {id: "s-877011574622138419", name: "web_scrapping"}, 70 | {id: "s-877012959233212506", name: "nextjs"}, 71 | {id: "s-877131907538780160", name: "rust"}, 72 | {id: "s-877132046164693012", name: "c_"}, 73 | {id: "s-877132109242851359", name: "cpp"}, 74 | {id: "s-877132754184204329", name: "academic_writing"}, 75 | {id: "s-877318526212378685", name: "illustration_infographics"}, 76 | {id: "s-877318643367677992", name: "brand_strategy"}, 77 | {id: "s-877319137263763486", name: "content_strategy"}, 78 | {id: "s-877320127547326484", name: "usability_testing"}, 79 | {id: "s-877369555096322070", name: "voice_over"}, 80 | {id: "s-878024872566071326", name: "mongodb"}, 81 | {id: "s-878125101646766100", name: "photo_editing"}, 82 | {id: "s-878126023990972436", name: "audio_production"}, 83 | {id: "s-882349698084638851", name: "memes"}, 84 | {id: "s-882350090654711808", name: "video_production"}, 85 | {id: "s-882350258259116152", name: "seo"}, 86 | ] 87 | 88 | var people_skills = [ 89 | {person: "p-359701069946814487", skill: "s-876871385442574416"}, 90 | {person: "p-266231791369715713", skill: "s-876871217536200747"}, 91 | {person: "p-282788499701104640", skill: "s-876868335298551828"}, 92 | {person: "p-801539080801222727", skill: "s-876867528226385930"}, 93 | {person: "p-299856078894661633", skill: "s-876871335232557106"}, 94 | {person: "p-786217377447804988", skill: "s-877010750030372864"}, 95 | {person: "p-282788499701104640", skill: "s-876871385442574416"}, 96 | {person: "p-776482013162111028", skill: "s-876581941082730518"}, 97 | {person: "p-571699490357116940", skill: "s-876871335232557106"}, 98 | {person: "p-388679223302291458", skill: "s-876910982457331783"}, 99 | {person: "p-299856078894661633", skill: "s-876871774162260048"}, 100 | {person: "p-403619359869894656", skill: "s-877012959233212506"}, 101 | {person: "p-281946480787324938", skill: "s-876967169836060682"}, 102 | {person: "p-125227954098667520", skill: "s-876871217536200747"}, 103 | {person: "p-266231791369715713", skill: "s-876871844110692413"}, 104 | {person: "p-795751965857677312", skill: "s-876910812915179570"}, 105 | {person: "p-801539080801222727", skill: "s-877132046164693012"}, 106 | {person: "p-176162785460224002", skill: "s-876868259629113414"}, 107 | {person: "p-266231791369715713", skill: "s-876910949934706728"}, 108 | {person: "p-848987946266525706", skill: "s-877318643367677992"}, 109 | {person: "p-176162785460224002", skill: "s-876871335232557106"}, 110 | {person: "p-578349648985718806", skill: "s-876871335232557106"}, 111 | {person: "p-388679223302291458", skill: "s-877011574622138419"}, 112 | {person: "p-393498292308410411", skill: "s-876910982457331783"}, 113 | {person: "p-719003771027324939", skill: "s-876868124379603024"}, 114 | {person: "p-176141900229771265", skill: "s-876910982457331783"}, 115 | {person: "p-426479599338127390", skill: "s-877318643367677992"}, 116 | {person: "p-230346367690604544", skill: "s-876581990994952242"}, 117 | {person: "p-776482013162111028", skill: "s-876871335232557106"}, 118 | {person: "p-801539080801222727", skill: "s-876910812915179570"}, 119 | {person: "p-848987946266525706", skill: "s-878125101646766100"}, 120 | {person: "p-795751965857677312", skill: "s-877012959233212506"}, 121 | {person: "p-732794021801492490", skill: "s-876581990994952242"}, 122 | {person: "p-388679223302291458", skill: "s-876581941082730518"}, 123 | {person: "p-398927951653109780", skill: "s-877320127547326484"}, 124 | {person: "p-801539080801222727", skill: "s-876967169836060682"}, 125 | {person: "p-278739943524925440", skill: "s-876871335232557106"}, 126 | {person: "p-176162785460224002", skill: "s-876910982457331783"}, 127 | {person: "p-707877605025644586", skill: "s-878126023990972436"}, 128 | {person: "p-719003771027324939", skill: "s-876871385442574416"}, 129 | {person: "p-253511881438920704", skill: "s-878125101646766100"}, 130 | {person: "p-758329214834311251", skill: "s-877011383974244413"}, 131 | {person: "p-230346367690604544", skill: "s-876868010466488400"}, 132 | {person: "p-578349648985718806", skill: "s-877011238104744028"}, 133 | {person: "p-776482013162111028", skill: "s-876871862863421543"}, 134 | {person: "p-388679223302291458", skill: "s-876868010466488400"}, 135 | {person: "p-732794021801492490", skill: "s-876868124379603024"}, 136 | {person: "p-281946480787324938", skill: "s-876868124379603024"}, 137 | {person: "p-403619359869894656", skill: "s-876871385442574416"}, 138 | {person: "p-384516597475180545", skill: "s-876910982457331783"}, 139 | {person: "p-776482013162111028", skill: "s-876910982457331783"}, 140 | {person: "p-801539080801222727", skill: "s-877012959233212506"}, 141 | {person: "p-281946480787324938", skill: "s-876871217536200747"}, 142 | {person: "p-795751965857677312", skill: "s-876867628910653450"}, 143 | {person: "p-429611433685417986", skill: "s-877011238104744028"}, 144 | {person: "p-266231791369715713", skill: "s-876867646447050814"}, 145 | {person: "p-571699490357116940", skill: "s-876867628910653450"}, 146 | {person: "p-795751965857677312", skill: "s-876868124379603024"}, 147 | {person: "p-328657481481977871", skill: "s-876871385442574416"}, 148 | {person: "p-564070757856968704", skill: "s-876871385442574416"}, 149 | {person: "p-691275910808338444", skill: "s-877318643367677992"}, 150 | {person: "p-359701069946814487", skill: "s-876871217536200747"}, 151 | {person: "p-578349648985718806", skill: "s-876581990994952242"}, 152 | {person: "p-282788499701104640", skill: "s-876868124379603024"}, 153 | {person: "p-403619359869894656", skill: "s-876967169836060682"}, 154 | {person: "p-281946480787324938", skill: "s-876871844110692413"}, 155 | {person: "p-732794021801492490", skill: "s-876910949934706728"}, 156 | {person: "p-571699490357116940", skill: "s-876868335298551828"}, 157 | {person: "p-795751965857677312", skill: "s-876871385442574416"}, 158 | {person: "p-299856078894661633", skill: "s-876871385442574416"}, 159 | {person: "p-374606417488904192", skill: "s-876867434827624469"}, 160 | {person: "p-328657481481977871", skill: "s-876910812915179570"}, 161 | {person: "p-281946480787324938", skill: "s-876910949934706728"}, 162 | {person: "p-429611433685417986", skill: "s-876581990994952242"}, 163 | {person: "p-801539080801222727", skill: "s-876868124379603024"}, 164 | {person: "p-426479599338127390", skill: "s-876868010466488400"}, 165 | {person: "p-795751965857677312", skill: "s-876871844110692413"}, 166 | {person: "p-176141900229771265", skill: "s-876871385442574416"}, 167 | {person: "p-564070757856968704", skill: "s-876967169836060682"}, 168 | {person: "p-275681329155407873", skill: "s-876868010466488400"}, 169 | {person: "p-820300955286241290", skill: "s-877010750030372864"}, 170 | {person: "p-393498292308410411", skill: "s-876871335232557106"}, 171 | {person: "p-359701069946814487", skill: "s-876910949934706728"}, 172 | {person: "p-266231791369715713", skill: "s-876871862863421543"}, 173 | {person: "p-374606417488904192", skill: "s-876868259629113414"}, 174 | {person: "p-282788499701104640", skill: "s-876871844110692413"}, 175 | {person: "p-776482013162111028", skill: "s-876867528226385930"}, 176 | {person: "p-388679223302291458", skill: "s-877011238104744028"}, 177 | {person: "p-578349648985718806", skill: "s-876871385442574416"}, 178 | {person: "p-801539080801222727", skill: "s-876871385442574416"}, 179 | {person: "p-578349648985718806", skill: "s-876871844110692413"}, 180 | {person: "p-801539080801222727", skill: "s-876871844110692413"}, 181 | {person: "p-571699490357116940", skill: "s-876967169836060682"}, 182 | {person: "p-176141900229771265", skill: "s-876967169836060682"}, 183 | {person: "p-388679223302291458", skill: "s-876581990994952242"}, 184 | {person: "p-403619359869894656", skill: "s-876868124379603024"}, 185 | {person: "p-359701069946814487", skill: "s-876581941082730518"}, 186 | {person: "p-253511881438920704", skill: "s-877369555096322070"}, 187 | {person: "p-278739943524925440", skill: "s-876871385442574416"}, 188 | {person: "p-176162785460224002", skill: "s-876967169836060682"}, 189 | {person: "p-578349648985718806", skill: "s-876967169836060682"}, 190 | {person: "p-776482013162111028", skill: "s-876871774162260048"}, 191 | {person: "p-393498292308410411", skill: "s-877012959233212506"}, 192 | {person: "p-719003771027324939", skill: "s-876871487691317318"}, 193 | {person: "p-281946480787324938", skill: "s-876867646447050814"}, 194 | {person: "p-253511881438920704", skill: "s-878126023990972436"}, 195 | {person: "p-578349648985718806", skill: "s-877011383974244413"}, 196 | {person: "p-776482013162111028", skill: "s-876910812915179570"}, 197 | {person: "p-266231791369715713", skill: "s-876581941082730518"}, 198 | {person: "p-281946480787324938", skill: "s-876868259629113414"}, 199 | {person: "p-776482013162111028", skill: "s-878024872566071326"}, 200 | {person: "p-564070757856968704", skill: "s-876868124379603024"}, 201 | {person: "p-703688028639461386", skill: "s-876581990994952242"}, 202 | {person: "p-691275910808338444", skill: "s-877132754184204329"}, 203 | {person: "p-176162785460224002", skill: "s-877012959233212506"}, 204 | {person: "p-732794021801492490", skill: "s-876871217536200747"}, 205 | {person: "p-288019500132073472", skill: "s-877132754184204329"}, 206 | {person: "p-719003771027324939", skill: "s-876910949934706728"}, 207 | {person: "p-758329214834311251", skill: "s-876581941082730518"}, 208 | {person: "p-393498292308410411", skill: "s-876867628910653450"}, 209 | {person: "p-281946480787324938", skill: "s-876871487691317318"}, 210 | {person: "p-429611433685417986", skill: "s-877131907538780160"}, 211 | {person: "p-299856078894661633", skill: "s-876868124379603024"}, 212 | {person: "p-282788499701104640", skill: "s-876868259629113414"}, 213 | {person: "p-776482013162111028", skill: "s-877012959233212506"}, 214 | {person: "p-571699490357116940", skill: "s-876868124379603024"}, 215 | {person: "p-328657481481977871", skill: "s-876871844110692413"}, 216 | {person: "p-299856078894661633", skill: "s-876871217536200747"}, 217 | {person: "p-281946480787324938", skill: "s-876871862863421543"}, 218 | {person: "p-587937947756789760", skill: "s-877011574622138419"}, 219 | {person: "p-176141900229771265", skill: "s-876868124379603024"}, 220 | {person: "p-801539080801222727", skill: "s-876867646447050814"}, 221 | {person: "p-328657481481977871", skill: "s-876910949934706728"}, 222 | {person: "p-795751965857677312", skill: "s-876871487691317318"}, 223 | {person: "p-176141900229771265", skill: "s-876871217536200747"}, 224 | {person: "p-374606417488904192", skill: "s-876867528226385930"}, 225 | {person: "p-388679223302291458", skill: "s-876910812915179570"}, 226 | {person: "p-848987946266525706", skill: "s-876868335298551828"}, 227 | {person: "p-374606417488904192", skill: "s-876868010466488400"}, 228 | {person: "p-176162785460224002", skill: "s-876868124379603024"}, 229 | {person: "p-388679223302291458", skill: "s-876967169836060682"}, 230 | {person: "p-393498292308410411", skill: "s-876871385442574416"}, 231 | {person: "p-359701069946814487", skill: "s-876910982457331783"}, 232 | {person: "p-578349648985718806", skill: "s-876871217536200747"}, 233 | {person: "p-282788499701104640", skill: "s-876871862863421543"}, 234 | {person: "p-388679223302291458", skill: "s-877011383974244413"}, 235 | {person: "p-801539080801222727", skill: "s-876871217536200747"}, 236 | {person: "p-403619359869894656", skill: "s-876581941082730518"}, 237 | {person: "p-801539080801222727", skill: "s-877132109242851359"}, 238 | {person: "p-426479599338127390", skill: "s-876868335298551828"}, 239 | {person: "p-266231791369715713", skill: "s-876910982457331783"}, 240 | {person: "p-848987946266525706", skill: "s-877319137263763486"}, 241 | {person: "p-587937947756789760", skill: "s-876868010466488400"}, 242 | {person: "p-176162785460224002", skill: "s-876871385442574416"}, 243 | {person: "p-426479599338127390", skill: "s-877318526212378685"}, 244 | {person: "p-429611433685417986", skill: "s-876871217536200747"}, 245 | {person: "p-429611433685417986", skill: "s-877132109242851359"}, 246 | {person: "p-278739943524925440", skill: "s-876868124379603024"}, 247 | {person: "p-281946480787324938", skill: "s-876581941082730518"}, 248 | {person: "p-398927951653109780", skill: "s-876871487691317318"}, 249 | {person: "p-176162785460224002", skill: "s-876910949934706728"}, 250 | {person: "p-776482013162111028", skill: "s-876871385442574416"}, 251 | {person: "p-801539080801222727", skill: "s-876910949934706728"}, 252 | {person: "p-403619359869894656", skill: "s-876868259629113414"}, 253 | {person: "p-176162785460224002", skill: "s-878125101646766100"}, 254 | {person: "p-776482013162111028", skill: "s-876871844110692413"}, 255 | {person: "p-405561963037327361", skill: "s-876867628910653450"}, 256 | {person: "p-564070757856968704", skill: "s-876868259629113414"}, 257 | {person: "p-282788499701104640", skill: "s-876581941082730518"}, 258 | {person: "p-388679223302291458", skill: "s-876868124379603024"}, 259 | {person: "p-403619359869894656", skill: "s-876871487691317318"}, 260 | {person: "p-578349648985718806", skill: "s-877131907538780160"}, 261 | {person: "p-776482013162111028", skill: "s-876967169836060682"}, 262 | {person: "p-388679223302291458", skill: "s-876871217536200747"}, 263 | {person: "p-719003771027324939", skill: "s-876910982457331783"}, 264 | {person: "p-281946480787324938", skill: "s-876871335232557106"}, 265 | {person: "p-786217377447804988", skill: "s-877318526212378685"}, 266 | {person: "p-803455455912525834", skill: "s-882349698084638851"}, 267 | {person: "p-578349648985718806", skill: "s-876581941082730518"}, 268 | {person: "p-786217377447804988", skill: "s-877320127547326484"}, 269 | {person: "p-281946480787324938", skill: "s-876871774162260048"}, 270 | {person: "p-587937947756789760", skill: "s-877011238104744028"}, 271 | {person: "p-564070757856968704", skill: "s-876871487691317318"}, 272 | {person: "p-691275910808338444", skill: "s-877319137263763486"}, 273 | {person: "p-328657481481977871", skill: "s-876871862863421543"}, 274 | {person: "p-587937947756789760", skill: "s-877012959233212506"}, 275 | {person: "p-795751965857677312", skill: "s-876871335232557106"}, 276 | {person: "p-405561963037327361", skill: "s-876871844110692413"}, 277 | {person: "p-388679223302291458", skill: "s-876871844110692413"}, 278 | {person: "p-429611433685417986", skill: "s-876581941082730518"}, 279 | {person: "p-266231791369715713", skill: "s-876871385442574416"}, 280 | {person: "p-758329214834311251", skill: "s-876868010466488400"}, 281 | {person: "p-393498292308410411", skill: "s-876871217536200747"}, 282 | {person: "p-282788499701104640", skill: "s-876871335232557106"}, 283 | {person: "p-732794021801492490", skill: "s-876910982457331783"}, 284 | {person: "p-281946480787324938", skill: "s-876910982457331783"}, 285 | {person: "p-359701069946814487", skill: "s-876910812915179570"}, 286 | {person: "p-403619359869894656", skill: "s-877011574622138419"}, 287 | {person: "p-758329214834311251", skill: "s-876868335298551828"}, 288 | {person: "p-578349648985718806", skill: "s-876868259629113414"}, 289 | {person: "p-801539080801222727", skill: "s-876868259629113414"}, 290 | {person: "p-571699490357116940", skill: "s-876871487691317318"}, 291 | {person: "p-795751965857677312", skill: "s-876871862863421543"}, 292 | {person: "p-176141900229771265", skill: "s-876871487691317318"}, 293 | {person: "p-405561963037327361", skill: "s-876967169836060682"}, 294 | {person: "p-266231791369715713", skill: "s-876910812915179570"}, 295 | {person: "p-801539080801222727", skill: "s-876871335232557106"}, 296 | {person: "p-374606417488904192", skill: "s-876868335298551828"}, 297 | {person: "p-176162785460224002", skill: "s-876871217536200747"}, 298 | {person: "p-719003771027324939", skill: "s-876867528226385930"}, 299 | {person: "p-176162785460224002", skill: "s-876871487691317318"}, 300 | {person: "p-393498292308410411", skill: "s-876910949934706728"}, 301 | {person: "p-776482013162111028", skill: "s-876868124379603024"}, 302 | {person: "p-429611433685417986", skill: "s-877132754184204329"}, 303 | {person: "p-384516597475180545", skill: "s-876871217536200747"}, 304 | {person: "p-776482013162111028", skill: "s-876871217536200747"}, 305 | {person: "p-801539080801222727", skill: "s-876871862863421543"}, 306 | {person: "p-587937947756789760", skill: "s-876868335298551828"}, 307 | {person: "p-426479599338127390", skill: "s-877320127547326484"}, 308 | {person: "p-776482013162111028", skill: "s-876871487691317318"}, 309 | {person: "p-564070757856968704", skill: "s-876867528226385930"}, 310 | {person: "p-426479599338127390", skill: "s-878125101646766100"}, 311 | {person: "p-707877605025644586", skill: "s-878125101646766100"}, 312 | {person: "p-803455455912525834", skill: "s-877318526212378685"}, 313 | {person: "p-758329214834311251", skill: "s-877011238104744028"}, 314 | {person: "p-426479599338127390", skill: "s-877010750030372864"}, 315 | {person: "p-587937947756789760", skill: "s-876871844110692413"}, 316 | {person: "p-278739943524925440", skill: "s-876871487691317318"}, 317 | {person: "p-176162785460224002", skill: "s-877010750030372864"}, 318 | {person: "p-703688028639461386", skill: "s-876581941082730518"}, 319 | {person: "p-403619359869894656", skill: "s-876871335232557106"}, 320 | {person: "p-719003771027324939", skill: "s-876871774162260048"}, 321 | {person: "p-299856078894661633", skill: "s-876581941082730518"}, 322 | {person: "p-578349648985718806", skill: "s-877011574622138419"}, 323 | {person: "p-776482013162111028", skill: "s-876910949934706728"}, 324 | {person: "p-403619359869894656", skill: "s-876871774162260048"}, 325 | {person: "p-719003771027324939", skill: "s-876910812915179570"}, 326 | {person: "p-795751965857677312", skill: "s-876867528226385930"}, 327 | {person: "p-299856078894661633", skill: "s-876867528226385930"}, 328 | {person: "p-398927951653109780", skill: "s-877011238104744028"}, 329 | {person: "p-786217377447804988", skill: "s-876871487691317318"}, 330 | {person: "p-328657481481977871", skill: "s-876871335232557106"}, 331 | {person: "p-281946480787324938", skill: "s-876871385442574416"}, 332 | {person: "p-719003771027324939", skill: "s-876967169836060682"}, 333 | {person: "p-564070757856968704", skill: "s-876871335232557106"}, 334 | {person: "p-405561963037327361", skill: "s-876871217536200747"}, 335 | {person: "p-691275910808338444", skill: "s-877318526212378685"}, 336 | {person: "p-776482013162111028", skill: "s-877011574622138419"}, 337 | {person: "p-328657481481977871", skill: "s-876871774162260048"}, 338 | {person: "p-691275910808338444", skill: "s-877320127547326484"}, 339 | {person: "p-732794021801492490", skill: "s-876871774162260048"}, 340 | ] 341 | 342 | -------------------------------------------------------------------------------- /examples/data/raid_guild-s3.js: -------------------------------------------------------------------------------- 1 | var people = [ 2 | {id: "p-84818148640972800", name: "0xSumna"}, 3 | {id: "p-102696242257084416", name: "uschtwill"}, 4 | {id: "p-170995435773624321", name: "technicallyty"}, 5 | {id: "p-196010287772598272", name: "Crypno"}, 6 | {id: "p-215951775318933505", name: "DAppaDan"}, 7 | {id: "p-227825347796664320", name: "Jip"}, 8 | {id: "p-230932578515943425", name: "MSK"}, 9 | {id: "p-233401126081265677", name: "zberwaldt"}, 10 | {id: "p-253511881438920704", name: "Liquidiot"}, 11 | {id: "p-276101014292267008", name: "solumos"}, 12 | {id: "p-287285429546385408", name: "mackenzie"}, 13 | {id: "p-309735511461724171", name: "ApeironCreations"}, 14 | {id: "p-325079380214939652", name: "Killmonger"}, 15 | {id: "p-352293372381364245", name: "willjuergens"}, 16 | {id: "p-353274308434591746", name: "Dallascat | Chris"}, 17 | {id: "p-355855602825822208", name: "williams"}, 18 | {id: "p-376593932701794304", name: "Adam Rideout"}, 19 | {id: "p-385240349641277442", name: "dinfo.eth"}, 20 | {id: "p-392419268454776832", name: "0xBeshoy"}, 21 | {id: "p-397809066115727383", name: "wackerow"}, 22 | {id: "p-428341571864231957", name: "swaHili"}, 23 | {id: "p-447315691226398733", name: "flip"}, 24 | {id: "p-458416050761891840", name: "MrDeadce11"}, 25 | {id: "p-468381615794421761", name: "hruday"}, 26 | {id: "p-469142548997341205", name: "bluewhiteorange"}, 27 | {id: "p-478990731038425109", name: "Parv"}, 28 | {id: "p-521689698893299713", name: "Sushi"}, 29 | {id: "p-522894357108621343", name: "brendan dev_group"}, 30 | {id: "p-555080144855760918", name: "gaia dadabit"}, 31 | {id: "p-574636026014990336", name: "ECWireless"}, 32 | {id: "p-575225590106554368", name: "itzr"}, 33 | {id: "p-582353800275296269", name: "Rory"}, 34 | {id: "p-604703891938082846", name: "Cdds"}, 35 | {id: "p-636261045606023169", name: "taekikz"}, 36 | {id: "p-656336009621012490", name: "Sunny"}, 37 | {id: "p-690684791259004989", name: "Corwintines"}, 38 | {id: "p-691275910808338444", name: "TravisWyche"}, 39 | {id: "p-693179352539463710", name: "itseva"}, 40 | {id: "p-696603843080749116", name: "OverAchiever"}, 41 | {id: "p-703688028639461386", name: "mprime"}, 42 | {id: "p-722405074030035007", name: "rplust"}, 43 | {id: "p-734907331711729734", name: "landrhymer"}, 44 | {id: "p-749152252966469668", name: "Tiki"}, 45 | {id: "p-757992642314502296", name: "jahabeebs"}, 46 | {id: "p-775509817250676737", name: "HHH"}, 47 | {id: "p-783542045687414824", name: "Sean dB"}, 48 | {id: "p-808364903651475477", name: "Torgmeister11"}, 49 | {id: "p-811775952522706944", name: "mok"}, 50 | {id: "p-814289381002575902", name: "lucascrespo"}, 51 | {id: "p-859322195549224960", name: "krgrs.eth"}, 52 | {id: "p-880971639163076608", name: "BorrowLucid | PT"}, 53 | {id: "p-897633822848454667", name: "creatorcharlie"}, 54 | {id: "p-900884428438532096", name: "launchninja"}, 55 | {id: "p-905293516324896798", name: "govinda"}, 56 | ] 57 | 58 | var skills = [ 59 | {id: "s-907411822053830656", name: "python"}, 60 | {id: "s-907411869457866753", name: "discord_bot"}, 61 | {id: "s-907411966266605608", name: "backend"}, 62 | {id: "s-907445596896497744", name: "philosophy"}, 63 | {id: "s-907445719353425940", name: "ux_design"}, 64 | {id: "s-907445756783382579", name: "fine_art"}, 65 | {id: "s-907445851197161492", name: "permaculture"}, 66 | {id: "s-907445956503552101", name: "metaverse_semantics"}, 67 | {id: "s-907446060480340080", name: "radical_divergence"}, 68 | {id: "s-908063912417706006", name: "typescript"}, 69 | {id: "s-908064434642108427", name: "kubernetes"}, 70 | {id: "s-908064649193357342", name: "javascript"}, 71 | {id: "s-908064736954949703", name: "google_cloud_platform"}, 72 | {id: "s-908064782123405334", name: "amazon_web_services"}, 73 | {id: "s-908064795683594241", name: "jenkins"}, 74 | {id: "s-908064817334603900", name: "terraform"}, 75 | {id: "s-908064870640005121", name: "circle_ci"}, 76 | {id: "s-908065706107621377", name: "ansible"}, 77 | {id: "s-908068470808252527", name: "react"}, 78 | {id: "s-908068515540525096", name: "node"}, 79 | {id: "s-908068597002297365", name: "solidity"}, 80 | {id: "s-908068643655524442", name: "clarity"}, 81 | {id: "s-908098664625491989", name: "lang_german"}, 82 | {id: "s-908120921804513360", name: "marketing"}, 83 | {id: "s-908121036539719710", name: "content_creation"}, 84 | {id: "s-908157558391463936", name: "golang"}, 85 | {id: "s-908157647063224373", name: "svelte"}, 86 | {id: "s-908162593594703953", name: "acctg_finance"}, 87 | {id: "s-908162699404378142", name: "ms_excel"}, 88 | {id: "s-908164989880254546", name: "financial_reporting"}, 89 | {id: "s-908165097275400224", name: "financial_modeling"}, 90 | {id: "s-908165179001426011", name: "us_tax"}, 91 | {id: "s-908165280117719060", name: "usa_tax"}, 92 | {id: "s-908165594333999144", name: "azure"}, 93 | {id: "s-908165996974571570", name: "chainlink"}, 94 | {id: "s-908166131695648798", name: "sql"}, 95 | {id: "s-908166259940659220", name: "mongo"}, 96 | {id: "s-908167181332799498", name: "mycology"}, 97 | {id: "s-908167367840903238", name: "fundraising"}, 98 | {id: "s-908167850294931537", name: "postgres"}, 99 | {id: "s-908168374092185641", name: "memelord"}, 100 | {id: "s-908181118388432926", name: "metabase"}, 101 | {id: "s-908181375327281213", name: "tableau"}, 102 | {id: "s-908201637120716800", name: "lang_spanish"}, 103 | {id: "s-908202301376847902", name: "leadership_development"}, 104 | {id: "s-908202343680573520", name: "org_development"}, 105 | {id: "s-908202416523051028", name: "community"}, 106 | {id: "s-908202945714225212", name: "podcasting"}, 107 | {id: "s-908406792541843467", name: "technical_writing"}, 108 | {id: "s-908406889400909876", name: "content_writing"}, 109 | {id: "s-908475220044697631", name: "sustainability"}, 110 | {id: "s-908475375510781953", name: "research"}, 111 | {id: "s-908475628309872720", name: "video_editing"}, 112 | {id: "s-908476126765129729", name: "illustration"}, 113 | {id: "s-908476157232566272", name: "design"}, 114 | {id: "s-908481952242356315", name: "solution_design"}, 115 | {id: "s-908557841567187005", name: "dad_jokes"}, 116 | {id: "s-908558104600379453", name: "voice_over"}, 117 | {id: "s-908558613063282719", name: "photo_editing"}, 118 | {id: "s-908850235239972904", name: "skill"}, 119 | {id: "s-908850410922602548", name: "non_technical_writing"}, 120 | {id: "s-909655015235407892", name: "graphql"}, 121 | {id: "s-909663932715765800", name: "html_css"}, 122 | {id: "s-909675196825341982", name: "hardhat"}, 123 | {id: "s-909691187475128330", name: "dapptools"}, 124 | {id: "s-909776863449210891", name: "substrate"}, 125 | {id: "s-909853396964630538", name: "superfluid"}, 126 | {id: "s-909853707229855754", name: "ethers"}, 127 | {id: "s-910020827444039741", name: "typescript_react_solidity_node_content_writing_research"}, 128 | {id: "s-910120722041540628", name: "frontend"}, 129 | {id: "s-910551398821789717", name: "product"}, 130 | {id: "s-910552114151960587", name: "bizdev"}, 131 | {id: "s-910552522631053312", name: "proposition"}, 132 | {id: "s-910552819591942174", name: "mycelium"}, 133 | {id: "s-911786345406554112", name: "sass_scss"}, 134 | {id: "s-918741758479835167", name: "wordpress"}, 135 | {id: "s-929513823860310097", name: "copywriting"}, 136 | {id: "s-929514275414892544", name: "brand_strategy"}, 137 | {id: "s-929514350484545596", name: "community_strategy"}, 138 | {id: "s-929514566080167956", name: "content_strategy"}, 139 | {id: "s-929514830744911932", name: "social_media"}, 140 | {id: "s-929515599376293968", name: "flavor_text"}, 141 | {id: "s-929515959306313759", name: "fiction_writing"}, 142 | {id: "s-930541630879043594", name: "vue"}, 143 | {id: "s-930541706166825050", name: "tailwind"}, 144 | {id: "s-941511598017753129", name: "substack"}, 145 | ] 146 | 147 | var people_skills = [ 148 | {person: "p-458416050761891840", skill: "s-908068597002297365"}, 149 | {person: "p-170995435773624321", skill: "s-908157647063224373"}, 150 | {person: "p-575225590106554368", skill: "s-908068515540525096"}, 151 | {person: "p-353274308434591746", skill: "s-908121036539719710"}, 152 | {person: "p-722405074030035007", skill: "s-908164989880254546"}, 153 | {person: "p-102696242257084416", skill: "s-908064736954949703"}, 154 | {person: "p-102696242257084416", skill: "s-908064817334603900"}, 155 | {person: "p-811775952522706944", skill: "s-907445851197161492"}, 156 | {person: "p-102696242257084416", skill: "s-908068470808252527"}, 157 | {person: "p-703688028639461386", skill: "s-907411822053830656"}, 158 | {person: "p-230932578515943425", skill: "s-908167850294931537"}, 159 | {person: "p-555080144855760918", skill: "s-907445596896497744"}, 160 | {person: "p-458416050761891840", skill: "s-909663932715765800"}, 161 | {person: "p-693179352539463710", skill: "s-908202301376847902"}, 162 | {person: "p-604703891938082846", skill: "s-907411966266605608"}, 163 | {person: "p-604703891938082846", skill: "s-908068597002297365"}, 164 | {person: "p-392419268454776832", skill: "s-907411822053830656"}, 165 | {person: "p-478990731038425109", skill: "s-908068515540525096"}, 166 | {person: "p-230932578515943425", skill: "s-908068470808252527"}, 167 | {person: "p-385240349641277442", skill: "s-908202945714225212"}, 168 | {person: "p-811775952522706944", skill: "s-910120722041540628"}, 169 | {person: "p-880971639163076608", skill: "s-929514350484545596"}, 170 | {person: "p-880971639163076608", skill: "s-929515599376293968"}, 171 | {person: "p-636261045606023169", skill: "s-910552819591942174"}, 172 | {person: "p-253511881438920704", skill: "s-908558613063282719"}, 173 | {person: "p-522894357108621343", skill: "s-909663932715765800"}, 174 | {person: "p-469142548997341205", skill: "s-908064649193357342"}, 175 | {person: "p-880971639163076608", skill: "s-908850410922602548"}, 176 | {person: "p-690684791259004989", skill: "s-908202416523051028"}, 177 | {person: "p-859322195549224960", skill: "s-908068470808252527"}, 178 | {person: "p-900884428438532096", skill: "s-908121036539719710"}, 179 | {person: "p-353274308434591746", skill: "s-908475375510781953"}, 180 | {person: "p-859322195549224960", skill: "s-908068643655524442"}, 181 | {person: "p-458416050761891840", skill: "s-908165996974571570"}, 182 | {person: "p-691275910808338444", skill: "s-907445851197161492"}, 183 | {person: "p-353274308434591746", skill: "s-908476157232566272"}, 184 | {person: "p-749152252966469668", skill: "s-907411966266605608"}, 185 | {person: "p-376593932701794304", skill: "s-907411822053830656"}, 186 | {person: "p-690684791259004989", skill: "s-908068515540525096"}, 187 | {person: "p-376593932701794304", skill: "s-908068470808252527"}, 188 | {person: "p-170995435773624321", skill: "s-908168374092185641"}, 189 | {person: "p-574636026014990336", skill: "s-908064649193357342"}, 190 | {person: "p-555080144855760918", skill: "s-910552522631053312"}, 191 | {person: "p-355855602825822208", skill: "s-908162699404378142"}, 192 | {person: "p-170995435773624321", skill: "s-907411869457866753"}, 193 | {person: "p-811775952522706944", skill: "s-907411966266605608"}, 194 | {person: "p-170995435773624321", skill: "s-908068515540525096"}, 195 | {person: "p-811775952522706944", skill: "s-908068597002297365"}, 196 | {person: "p-353274308434591746", skill: "s-908068597002297365"}, 197 | {person: "p-233401126081265677", skill: "s-908068515540525096"}, 198 | {person: "p-428341571864231957", skill: "s-908167850294931537"}, 199 | {person: "p-575225590106554368", skill: "s-908068470808252527"}, 200 | {person: "p-102696242257084416", skill: "s-908064649193357342"}, 201 | {person: "p-783542045687414824", skill: "s-908202416523051028"}, 202 | {person: "p-749152252966469668", skill: "s-909776863449210891"}, 203 | {person: "p-102696242257084416", skill: "s-908064795683594241"}, 204 | {person: "p-703688028639461386", skill: "s-908064795683594241"}, 205 | {person: "p-604703891938082846", skill: "s-908165996974571570"}, 206 | {person: "p-690684791259004989", skill: "s-909853707229855754"}, 207 | {person: "p-325079380214939652", skill: "s-908068515540525096"}, 208 | {person: "p-230932578515943425", skill: "s-908064649193357342"}, 209 | {person: "p-276101014292267008", skill: "s-907411966266605608"}, 210 | {person: "p-170995435773624321", skill: "s-909691187475128330"}, 211 | {person: "p-276101014292267008", skill: "s-908068597002297365"}, 212 | {person: "p-690684791259004989", skill: "s-908406889400909876"}, 213 | {person: "p-385240349641277442", skill: "s-908202416523051028"}, 214 | {person: "p-521689698893299713", skill: "s-908168374092185641"}, 215 | {person: "p-880971639163076608", skill: "s-929514275414892544"}, 216 | {person: "p-691275910808338444", skill: "s-907445756783382579"}, 217 | {person: "p-757992642314502296", skill: "s-908121036539719710"}, 218 | {person: "p-253511881438920704", skill: "s-908558104600379453"}, 219 | {person: "p-521689698893299713", skill: "s-908068515540525096"}, 220 | {person: "p-859322195549224960", skill: "s-908064649193357342"}, 221 | {person: "p-84818148640972800", skill: "s-907411966266605608"}, 222 | {person: "p-84818148640972800", skill: "s-908068597002297365"}, 223 | {person: "p-376593932701794304", skill: "s-908064649193357342"}, 224 | {person: "p-900884428438532096", skill: "s-908120921804513360"}, 225 | {person: "p-690684791259004989", skill: "s-907411822053830656"}, 226 | {person: "p-253511881438920704", skill: "s-908202945714225212"}, 227 | {person: "p-690684791259004989", skill: "s-908068470808252527"}, 228 | {person: "p-352293372381364245", skill: "s-908558104600379453"}, 229 | {person: "p-811775952522706944", skill: "s-908167181332799498"}, 230 | {person: "p-722405074030035007", skill: "s-908167850294931537"}, 231 | {person: "p-458416050761891840", skill: "s-908063912417706006"}, 232 | {person: "p-722405074030035007", skill: "s-908181375327281213"}, 233 | {person: "p-355855602825822208", skill: "s-908162593594703953"}, 234 | {person: "p-775509817250676737", skill: "s-909663932715765800"}, 235 | {person: "p-811775952522706944", skill: "s-908064782123405334"}, 236 | {person: "p-722405074030035007", skill: "s-907411822053830656"}, 237 | {person: "p-352293372381364245", skill: "s-908202945714225212"}, 238 | {person: "p-811775952522706944", skill: "s-908068515540525096"}, 239 | {person: "p-233401126081265677", skill: "s-907411822053830656"}, 240 | {person: "p-233401126081265677", skill: "s-908068470808252527"}, 241 | {person: "p-352293372381364245", skill: "s-907445756783382579"}, 242 | {person: "p-522894357108621343", skill: "s-908063912417706006"}, 243 | {person: "p-428341571864231957", skill: "s-908064649193357342"}, 244 | {person: "p-690684791259004989", skill: "s-909675196825341982"}, 245 | {person: "p-775509817250676737", skill: "s-908162699404378142"}, 246 | {person: "p-604703891938082846", skill: "s-908165594333999144"}, 247 | {person: "p-814289381002575902", skill: "s-908121036539719710"}, 248 | {person: "p-582353800275296269", skill: "s-908068515540525096"}, 249 | {person: "p-325079380214939652", skill: "s-908068470808252527"}, 250 | {person: "p-604703891938082846", skill: "s-908063912417706006"}, 251 | {person: "p-276101014292267008", skill: "s-908064782123405334"}, 252 | {person: "p-227825347796664320", skill: "s-907411966266605608"}, 253 | {person: "p-276101014292267008", skill: "s-908064870640005121"}, 254 | {person: "p-170995435773624321", skill: "s-909675196825341982"}, 255 | {person: "p-385240349641277442", skill: "s-908181375327281213"}, 256 | {person: "p-84818148640972800", skill: "s-908165996974571570"}, 257 | {person: "p-905293516324896798", skill: "s-908475375510781953"}, 258 | {person: "p-757992642314502296", skill: "s-907411966266605608"}, 259 | {person: "p-656336009621012490", skill: "s-908481952242356315"}, 260 | {person: "p-385240349641277442", skill: "s-907411822053830656"}, 261 | {person: "p-905293516324896798", skill: "s-908476157232566272"}, 262 | {person: "p-521689698893299713", skill: "s-908064736954949703"}, 263 | {person: "p-691275910808338444", skill: "s-907445719353425940"}, 264 | {person: "p-880971639163076608", skill: "s-908406889400909876"}, 265 | {person: "p-287285429546385408", skill: "s-907411966266605608"}, 266 | {person: "p-521689698893299713", skill: "s-908068470808252527"}, 267 | {person: "p-749152252966469668", skill: "s-908063912417706006"}, 268 | {person: "p-690684791259004989", skill: "s-908064649193357342"}, 269 | {person: "p-656336009621012490", skill: "s-908202343680573520"}, 270 | {person: "p-352293372381364245", skill: "s-908475220044697631"}, 271 | {person: "p-352293372381364245", skill: "s-908476126765129729"}, 272 | {person: "p-253511881438920704", skill: "s-908202416523051028"}, 273 | {person: "p-808364903651475477", skill: "s-908202416523051028"}, 274 | {person: "p-722405074030035007", skill: "s-908166131695648798"}, 275 | {person: "p-722405074030035007", skill: "s-908167367840903238"}, 276 | {person: "p-811775952522706944", skill: "s-908063912417706006"}, 277 | {person: "p-905293516324896798", skill: "s-908120921804513360"}, 278 | {person: "p-170995435773624321", skill: "s-908064649193357342"}, 279 | {person: "p-604703891938082846", skill: "s-908202301376847902"}, 280 | {person: "p-808364903651475477", skill: "s-908162699404378142"}, 281 | {person: "p-352293372381364245", skill: "s-908202416523051028"}, 282 | {person: "p-233401126081265677", skill: "s-908064649193357342"}, 283 | {person: "p-574636026014990336", skill: "s-910120722041540628"}, 284 | {person: "p-897633822848454667", skill: "s-907445719353425940"}, 285 | {person: "p-325079380214939652", skill: "s-908166131695648798"}, 286 | {person: "p-690684791259004989", skill: "s-908557841567187005"}, 287 | {person: "p-392419268454776832", skill: "s-908162593594703953"}, 288 | {person: "p-690684791259004989", skill: "s-909663932715765800"}, 289 | {person: "p-325079380214939652", skill: "s-908064649193357342"}, 290 | {person: "p-814289381002575902", skill: "s-908120921804513360"}, 291 | {person: "p-276101014292267008", skill: "s-908063912417706006"}, 292 | {person: "p-227825347796664320", skill: "s-908064870640005121"}, 293 | {person: "p-276101014292267008", skill: "s-908064736954949703"}, 294 | {person: "p-385240349641277442", skill: "s-908166131695648798"}, 295 | {person: "p-469142548997341205", skill: "s-908068597002297365"}, 296 | {person: "p-102696242257084416", skill: "s-910120722041540628"}, 297 | {person: "p-385240349641277442", skill: "s-908167367840903238"}, 298 | {person: "p-521689698893299713", skill: "s-908166131695648798"}, 299 | {person: "p-84818148640972800", skill: "s-908165594333999144"}, 300 | {person: "p-905293516324896798", skill: "s-908475220044697631"}, 301 | {person: "p-385240349641277442", skill: "s-908064795683594241"}, 302 | {person: "p-808364903651475477", skill: "s-908406889400909876"}, 303 | {person: "p-521689698893299713", skill: "s-908064649193357342"}, 304 | {person: "p-84818148640972800", skill: "s-908063912417706006"}, 305 | {person: "p-574636026014990336", skill: "s-907411966266605608"}, 306 | {person: "p-783542045687414824", skill: "s-908557841567187005"}, 307 | {person: "p-196010287772598272", skill: "s-907411822053830656"}, 308 | {person: "p-355855602825822208", skill: "s-908167181332799498"}, 309 | {person: "p-690684791259004989", skill: "s-907445956503552101"}, 310 | {person: "p-170995435773624321", skill: "s-908157558391463936"}, 311 | {person: "p-775509817250676737", skill: "s-918741758479835167"}, 312 | {person: "p-905293516324896798", skill: "s-908168374092185641"}, 313 | {person: "p-352293372381364245", skill: "s-908406889400909876"}, 314 | {person: "p-775509817250676737", skill: "s-908406792541843467"}, 315 | {person: "p-808364903651475477", skill: "s-908202343680573520"}, 316 | {person: "p-102696242257084416", skill: "s-908065706107621377"}, 317 | {person: "p-102696242257084416", skill: "s-907411966266605608"}, 318 | {person: "p-703688028639461386", skill: "s-907411966266605608"}, 319 | {person: "p-196010287772598272", skill: "s-909675196825341982"}, 320 | {person: "p-604703891938082846", skill: "s-908068515540525096"}, 321 | {person: "p-693179352539463710", skill: "s-908202416523051028"}, 322 | {person: "p-230932578515943425", skill: "s-907411966266605608"}, 323 | {person: "p-276101014292267008", skill: "s-908157647063224373"}, 324 | {person: "p-392419268454776832", skill: "s-908068597002297365"}, 325 | {person: "p-469142548997341205", skill: "s-908165996974571570"}, 326 | {person: "p-690684791259004989", skill: "s-908558613063282719"}, 327 | {person: "p-693179352539463710", skill: "s-908098664625491989"}, 328 | {person: "p-325079380214939652", skill: "s-908064434642108427"}, 329 | {person: "p-227825347796664320", skill: "s-908063912417706006"}, 330 | {person: "p-859322195549224960", skill: "s-908068597002297365"}, 331 | {person: "p-749152252966469668", skill: "s-907411869457866753"}, 332 | {person: "p-757992642314502296", skill: "s-908063912417706006"}, 333 | {person: "p-575225590106554368", skill: "s-908475375510781953"}, 334 | {person: "p-691275910808338444", skill: "s-907446060480340080"}, 335 | {person: "p-376593932701794304", skill: "s-908068597002297365"}, 336 | {person: "p-690684791259004989", skill: "s-908121036539719710"}, 337 | {person: "p-555080144855760918", skill: "s-910552114151960587"}, 338 | {person: "p-196010287772598272", skill: "s-908064649193357342"}, 339 | {person: "p-355855602825822208", skill: "s-908165097275400224"}, 340 | {person: "p-397809066115727383", skill: "s-908063912417706006"}, 341 | {person: "p-170995435773624321", skill: "s-907411822053830656"}, 342 | {person: "p-353274308434591746", skill: "s-908202416523051028"}, 343 | {person: "p-722405074030035007", skill: "s-908162593594703953"}, 344 | {person: "p-575225590106554368", skill: "s-908068597002297365"}, 345 | {person: "p-102696242257084416", skill: "s-908064434642108427"}, 346 | {person: "p-102696242257084416", skill: "s-908064782123405334"}, 347 | {person: "p-703688028639461386", skill: "s-908064782123405334"}, 348 | {person: "p-522894357108621343", skill: "s-908068470808252527"}, 349 | {person: "p-102696242257084416", skill: "s-908064870640005121"}, 350 | {person: "p-604703891938082846", skill: "s-908166259940659220"}, 351 | {person: "p-458416050761891840", skill: "s-930541706166825050"}, 352 | {person: "p-428341571864231957", skill: "s-908068597002297365"}, 353 | {person: "p-703688028639461386", skill: "s-907411869457866753"}, 354 | {person: "p-690684791259004989", skill: "s-910120722041540628"}, 355 | {person: "p-574636026014990336", skill: "s-909655015235407892"}, 356 | {person: "p-458416050761891840", skill: "s-909675196825341982"}, 357 | {person: "p-276101014292267008", skill: "s-908068515540525096"}, 358 | {person: "p-690684791259004989", skill: "s-908406792541843467"}, 359 | {person: "p-604703891938082846", skill: "s-908068470808252527"}, 360 | {person: "p-385240349641277442", skill: "s-908202343680573520"}, 361 | {person: "p-478990731038425109", skill: "s-908068597002297365"}, 362 | {person: "p-880971639163076608", skill: "s-929513823860310097"}, 363 | {person: "p-276101014292267008", skill: "s-908157558391463936"}, 364 | {person: "p-353274308434591746", skill: "s-909853707229855754"}, 365 | {person: "p-880971639163076608", skill: "s-929514566080167956"}, 366 | {person: "p-385240349641277442", skill: "s-907445596896497744"}, 367 | {person: "p-253511881438920704", skill: "s-908557841567187005"}, 368 | {person: "p-880971639163076608", skill: "s-929515959306313759"}, 369 | {person: "p-521689698893299713", skill: "s-907411822053830656"}, 370 | {person: "p-905293516324896798", skill: "s-908850410922602548"}, 371 | {person: "p-84818148640972800", skill: "s-908068515540525096"}, 372 | {person: "p-859322195549224960", skill: "s-907411869457866753"}, 373 | {person: "p-859322195549224960", skill: "s-908068515540525096"}, 374 | {person: "p-749152252966469668", skill: "s-907411822053830656"}, 375 | {person: "p-749152252966469668", skill: "s-908068470808252527"}, 376 | {person: "p-691275910808338444", skill: "s-907445956503552101"}, 377 | {person: "p-690684791259004989", skill: "s-907411966266605608"}, 378 | {person: "p-775509817250676737", skill: "s-941511598017753129"}, 379 | {person: "p-690684791259004989", skill: "s-908068597002297365"}, 380 | {person: "p-574636026014990336", skill: "s-908063912417706006"}, 381 | {person: "p-555080144855760918", skill: "s-910551398821789717"}, 382 | {person: "p-814289381002575902", skill: "s-908476157232566272"}, 383 | {person: "p-722405074030035007", skill: "s-908181118388432926"}, 384 | {person: "p-458416050761891840", skill: "s-908064649193357342"}, 385 | {person: "p-355855602825822208", skill: "s-908164989880254546"}, 386 | {person: "p-811775952522706944", skill: "s-908068470808252527"}, 387 | {person: "p-170995435773624321", skill: "s-907411966266605608"}, 388 | {person: "p-170995435773624321", skill: "s-908068597002297365"}, 389 | {person: "p-522894357108621343", skill: "s-908064649193357342"}, 390 | {person: "p-215951775318933505", skill: "s-908850410922602548"}, 391 | {person: "p-458416050761891840", skill: "s-930541630879043594"}, 392 | {person: "p-276101014292267008", skill: "s-908167850294931537"}, 393 | {person: "p-900884428438532096", skill: "s-908406889400909876"}, 394 | {person: "p-352293372381364245", skill: "s-907445956503552101"}, 395 | {person: "p-325079380214939652", skill: "s-907411966266605608"}, 396 | {person: "p-458416050761891840", skill: "s-908557841567187005"}, 397 | {person: "p-227825347796664320", skill: "s-908068515540525096"}, 398 | {person: "p-604703891938082846", skill: "s-908064649193357342"}, 399 | {person: "p-230932578515943425", skill: "s-908063912417706006"}, 400 | {person: "p-276101014292267008", skill: "s-908064817334603900"}, 401 | {person: "p-757992642314502296", skill: "s-908201637120716800"}, 402 | {person: "p-276101014292267008", skill: "s-907411822053830656"}, 403 | {person: "p-447315691226398733", skill: "s-907411966266605608"}, 404 | {person: "p-276101014292267008", skill: "s-908068470808252527"}, 405 | {person: "p-811775952522706944", skill: "s-909675196825341982"}, 406 | {person: "p-468381615794421761", skill: "s-930541706166825050"}, 407 | {person: "p-385240349641277442", skill: "s-908202301376847902"}, 408 | {person: "p-353274308434591746", skill: "s-909675196825341982"}, 409 | {person: "p-757992642314502296", skill: "s-908068515540525096"}, 410 | {person: "p-253511881438920704", skill: "s-908475628309872720"}, 411 | {person: "p-353274308434591746", skill: "s-909853396964630538"}, 412 | {person: "p-905293516324896798", skill: "s-908476126765129729"}, 413 | {person: "p-691275910808338444", skill: "s-907445596896497744"}, 414 | {person: "p-905293516324896798", skill: "s-908557841567187005"}, 415 | {person: "p-859322195549224960", skill: "s-908063912417706006"}, 416 | {person: "p-196010287772598272", skill: "s-907445596896497744"}, 417 | {person: "p-521689698893299713", skill: "s-907411966266605608"}, 418 | {person: "p-521689698893299713", skill: "s-908068597002297365"}, 419 | {person: "p-690684791259004989", skill: "s-908168374092185641"}, 420 | {person: "p-84818148640972800", skill: "s-907411822053830656"}, 421 | {person: "p-749152252966469668", skill: "s-908064649193357342"}, 422 | {person: "p-376593932701794304", skill: "s-908063912417706006"}, 423 | {person: "p-352293372381364245", skill: "s-908475628309872720"}, 424 | {person: "p-352293372381364245", skill: "s-908481952242356315"}, 425 | {person: "p-775509817250676737", skill: "s-908850410922602548"}, 426 | {person: "p-253511881438920704", skill: "s-908121036539719710"}, 427 | {person: "p-811775952522706944", skill: "s-908064649193357342"}, 428 | {person: "p-353274308434591746", skill: "s-908167367840903238"}, 429 | {person: "p-353274308434591746", skill: "s-908064649193357342"}, 430 | {person: "p-352293372381364245", skill: "s-908202343680573520"}, 431 | {person: "p-575225590106554368", skill: "s-908063912417706006"}, 432 | {person: "p-309735511461724171", skill: "s-907445851197161492"}, 433 | {person: "p-352293372381364245", skill: "s-907445596896497744"}, 434 | {person: "p-84818148640972800", skill: "s-909675196825341982"}, 435 | {person: "p-352293372381364245", skill: "s-908121036539719710"}, 436 | {person: "p-897633822848454667", skill: "s-908121036539719710"}, 437 | {person: "p-749152252966469668", skill: "s-909663932715765800"}, 438 | {person: "p-690684791259004989", skill: "s-909655015235407892"}, 439 | {person: "p-276101014292267008", skill: "s-908166131695648798"}, 440 | {person: "p-469142548997341205", skill: "s-908201637120716800"}, 441 | {person: "p-696603843080749116", skill: "s-908068470808252527"}, 442 | {person: "p-582353800275296269", skill: "s-907411966266605608"}, 443 | {person: "p-582353800275296269", skill: "s-908068597002297365"}, 444 | {person: "p-276101014292267008", skill: "s-908064649193357342"}, 445 | {person: "p-469142548997341205", skill: "s-908068515540525096"}, 446 | {person: "p-227825347796664320", skill: "s-908068470808252527"}, 447 | {person: "p-811775952522706944", skill: "s-909663932715765800"}, 448 | {person: "p-392419268454776832", skill: "s-908064649193357342"}, 449 | {person: "p-468381615794421761", skill: "s-930541630879043594"}, 450 | {person: "p-905293516324896798", skill: "s-908406889400909876"}, 451 | {person: "p-84818148640972800", skill: "s-908166131695648798"}, 452 | {person: "p-808364903651475477", skill: "s-908406792541843467"}, 453 | {person: "p-905293516324896798", skill: "s-908475628309872720"}, 454 | {person: "p-808364903651475477", skill: "s-908475375510781953"}, 455 | {person: "p-574636026014990336", skill: "s-908068515540525096"}, 456 | {person: "p-196010287772598272", skill: "s-908068597002297365"}, 457 | {person: "p-775509817250676737", skill: "s-911786345406554112"}, 458 | {person: "p-690684791259004989", skill: "s-908063912417706006"}, 459 | {person: "p-325079380214939652", skill: "s-909655015235407892"}, 460 | {person: "p-722405074030035007", skill: "s-908165097275400224"}, 461 | {person: "p-734907331711729734", skill: "s-907411966266605608"}, 462 | {person: "p-469142548997341205", skill: "s-909853707229855754"}, 463 | {person: "p-353274308434591746", skill: "s-908165996974571570"}, 464 | {person: "p-170995435773624321", skill: "s-908063912417706006"}, 465 | {person: "p-102696242257084416", skill: "s-908098664625491989"}, 466 | {person: "p-808364903651475477", skill: "s-908120921804513360"}, 467 | {person: "p-352293372381364245", skill: "s-908202301376847902"}, 468 | {person: "p-233401126081265677", skill: "s-908063912417706006"}, 469 | {person: "p-574636026014990336", skill: "s-909853707229855754"}, 470 | {person: "p-693179352539463710", skill: "s-908202343680573520"}, 471 | {person: "p-582353800275296269", skill: "s-908165996974571570"}, 472 | {person: "p-230932578515943425", skill: "s-908068515540525096"}, 473 | {person: "p-276101014292267008", skill: "s-908162699404378142"}, 474 | {person: "p-696603843080749116", skill: "s-908064649193357342"}, 475 | {person: "p-325079380214939652", skill: "s-908063912417706006"}, 476 | {person: "p-227825347796664320", skill: "s-908064649193357342"}, 477 | {person: "p-757992642314502296", skill: "s-908166131695648798"}, 478 | {person: "p-469142548997341205", skill: "s-908068470808252527"}, 479 | {person: "p-859322195549224960", skill: "s-907445719353425940"}, 480 | {person: "p-574636026014990336", skill: "s-908166259940659220"}, 481 | {person: "p-84818148640972800", skill: "s-908162699404378142"}, 482 | {person: "p-757992642314502296", skill: "s-908064649193357342"}, 483 | {person: "p-309735511461724171", skill: "s-908475220044697631"}, 484 | {person: "p-555080144855760918", skill: "s-910552819591942174"}, 485 | {person: "p-355855602825822208", skill: "s-908165280117719060"}, 486 | {person: "p-397809066115727383", skill: "s-908064649193357342"}, 487 | ] 488 | 489 | -------------------------------------------------------------------------------- /examples/data/simple.js: -------------------------------------------------------------------------------- 1 | var people = [ 2 | {id: "alice", name: "Alice"}, 3 | {id: "bob", name: "Bob"}, 4 | {id: "charlie", name: "Charlie"}, 5 | {id: "diana", name: "Diana"}, 6 | ] 7 | 8 | var skills = [ 9 | {id: "wood", name: "Woodworking"}, 10 | {id: "fish", name: "Fishing"}, 11 | {id: "cook", name: "Cooking"}, 12 | {id: "garden", name: "Gardening"}, 13 | ] 14 | 15 | var people_skills = [ 16 | {person: "alice", skill: "fish"}, 17 | {person: "alice", skill: "cook"}, 18 | {person: "bob", skill: "garden"}, 19 | {person: "bob", skill: "wood"}, 20 | {person: "charlie", skill: "wood"}, 21 | {person: "charlie", skill: "fish"}, 22 | {person: "diana", skill: "garden"}, 23 | {person: "diana", skill: "fish"}, 24 | ] 25 | -------------------------------------------------------------------------------- /examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These are example of `skill-bot` in action. 4 | 5 | - [Simple](./simple) 6 | - [MetaGame](./metagame/) 7 | - [Raid Guild Season 0](./raidguild-s0/) 8 | - [Raid Guild Season 1](./raidguild-s1/) 9 | - [Raid Guild Season 2](./raidguild-s2/) 10 | - [Raid Guild Season 3](./raidguild-s3/) 11 | - [Raid Guild Season 4](./raidguild-s4/) 12 | 13 | 14 | *N.B.* data is **not updated in real-time**, it is just a snapshot. 15 | Generating these graphs is a manual process. 16 | 17 | A lille web app that fetches data from the live bot database is pretty simple, if you're up for it, please contribute it to the project! 18 | -------------------------------------------------------------------------------- /examples/metagame/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Metagame 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/raidguild-s0/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Raid Guild Season 0 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/raidguild-s1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Raid Guild Season 1 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/raidguild-s2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Raid Guild Season 2 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/raidguild-s3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Raid Guild Season 3 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/raidguild-s4/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Raid Guild Season 4 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Skillbot - Simple (test data) 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you can read this, something is broken 😰

16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /export.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import repository 6 | import model 7 | from render import * 8 | 9 | input_files = sys.argv[1:] 10 | 11 | print(f"Exporting {len(input_files)} databases...") 12 | 13 | for input_file in input_files: 14 | json_output_file = input_file + "-export.json" 15 | js_output_file = input_file + "-export.js" 16 | 17 | print(f"Reading {input_file}") 18 | repo = repository.SqliteRepository(input_file) 19 | gs = repo.get_graph_snapshot() 20 | 21 | print(f"Exporting {json_output_file}") 22 | graph = { 23 | 'people': [ 24 | { 25 | 'id': person.id, 26 | 'name': person.name, 27 | } for person in gs.people.values() 28 | ], 29 | 'skills': [ 30 | { 31 | 'id': skill.id, 32 | 'name': skill.name, 33 | } for skill in gs.skills.values() 34 | ], 35 | 'links': [ 36 | { 37 | 'person': link[0].id, 38 | 'skill': link[1].id, 39 | } for link in gs.people_skills 40 | ], 41 | } 42 | with open(json_output_file, 'w') as f: 43 | json.dump(graph, f, indent=2) 44 | 45 | print(f"Exporting {js_output_file}") 46 | with open(js_output_file, 'w') as f: 47 | f.write('var people = [\n') 48 | for p in gs.people.values(): 49 | f.write(' {id: "p-'+str(p.id)+'", name: "'+p.name+'"},\n') 50 | f.write(']\n') 51 | f.write('\n') 52 | f.write('var skills = [\n') 53 | for s in gs.skills.values(): 54 | f.write(' {id: "s-'+str(s.id)+'", name: "'+s.name+'"},\n') 55 | f.write(']\n') 56 | f.write('\n') 57 | f.write('var people_skills = [\n') 58 | for ps in gs.people_skills: 59 | f.write(' {person: "p-'+str(ps[0].id)+'", skill: "s-'+str(ps[1].id)+'"},\n') 60 | f.write(']\n') 61 | f.write('\n') 62 | -------------------------------------------------------------------------------- /misc/2021-metafest.md: -------------------------------------------------------------------------------- 1 | # 🤖 2 | # [skill-bot](https://github.com/MetaFam/skill-bot) 3 | 4 | #### Skills & Interests Discovery For Online Communities 5 | 6 | ### 🐙 7 | 8 | #### [MetaFest](http://metafest.metagame.wtf) 2021 9 | 10 | --- 11 | 12 | # The Invisible Web 13 | 14 | We are connecte in many more ways than we can imagine 15 | 16 | Any two people are linked by interests, experiences, opinions, ... 17 | 18 | Discovering this hidden link is hard. 19 | 20 | --- 21 | 22 | # Online Communities 23 | 24 | Even if we are "toghether" all day online, discoverying the hidden links is not trivial. 25 | 26 | It does happen serendipitously. 27 | 28 | Can we facilitate it? 29 | 30 | --- 31 | 32 | # Skill-bot 33 | 34 | A Discord bot to facilitate shared skills and interest discovery 35 | 36 | Skills and interest discovery as a fun multiplayer game 37 | 38 | --- 39 | 40 | ![self](https://github.com/MetaFam/skill-bot/raw/master/sample/self.jpg "mprime's skills and interests") 41 | 42 | --- 43 | 44 | ![interest](https://github.com/MetaFam/skill-bot/raw/master/sample/interest.jpg "Cooking & Tacos") 45 | 46 | --- 47 | 48 | ![overlap](https://github.com/MetaFam/skill-bot/raw/master/sample/overlap.jpg "Shared interests") 49 | 50 | --- 51 | 52 | ![skills](https://github.com/MetaFam/skill-bot/raw/master/sample/skill.jpg "Skill lookup") 53 | 54 | --- 55 | 56 | 57 | ![full graph](https://github.com/MetaFam/skill-bot/raw/master/sample/metagame.jpg "MetaGame") 58 | 59 | --- 60 | 61 | Thank you to all who played, reported issues, made suggestions. 62 | 63 | **https://github.com/MetaFam/skill-bot** 64 | 65 | * MetaGame > #skill-bot 66 | * RaidGuild > #season0-skills 67 | 68 | Made with ❤️, Python, Graphviz, SQLite by [mprime](www.mpri.me) 69 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | -------------------------------------------------------------------------------- /model/core.py: -------------------------------------------------------------------------------- 1 | class Person(object): 2 | """Represents a person with name and some skills""" 3 | 4 | def __init__(self, id: int, name: str): 5 | super(Person, self).__init__() 6 | self.id = id 7 | self.name = name 8 | self.skills = set() 9 | 10 | def add_skill(self, skill): 11 | self.skills.add(skill) 12 | 13 | class Skill(object): 14 | """Represents a skill (or interest), has a name and references people with this skill""" 15 | 16 | def __init__(self, id: int, name: str): 17 | super(Skill, self).__init__() 18 | self.id = id 19 | self.name = name 20 | self.people = set() 21 | 22 | def add_person(self, person): 23 | self.people.add(person) 24 | 25 | class Graph(object): 26 | """ 27 | Representation of the graph with cross-referencing objects and some indexing 28 | """ 29 | 30 | def __init__(self): 31 | super(Graph, self).__init__() 32 | self.people = {} 33 | self.skills = {} 34 | self.people_skills = set() 35 | 36 | def link_person_to_skill(self, person_id: int, skill_id: int): 37 | person = self.people[person_id] 38 | skill = self.skills[skill_id] 39 | person.add_skill(skill) 40 | skill.add_person(person) 41 | self.people_skills.add((person, skill)) 42 | 43 | def add_person(self, person: Person): 44 | self.people[person.id] = person 45 | 46 | def add_skill(self, skill: Skill): 47 | self.skills[skill.id] = skill 48 | -------------------------------------------------------------------------------- /render/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | -------------------------------------------------------------------------------- /render/core.py: -------------------------------------------------------------------------------- 1 | from graphviz import Digraph, Graph 2 | from graphviz.dot import Dot 3 | 4 | from constants import Cosmetics as c 5 | 6 | import model 7 | 8 | class FullGraphDotRenderer(object): 9 | """Renders the full graph into a graphviz.Digraph object""" 10 | 11 | def __init__(self, graph: model.Graph): 12 | super(FullGraphDotRenderer, self).__init__() 13 | self.graph = graph 14 | 15 | def render(self): 16 | graph_attributes = [ 17 | ('overlap', 'false'), 18 | ('layout', 'sfdp'), 19 | ('ranksep', '3'), 20 | ('bgcolor', 'black'), 21 | ('K', '0.6'), 22 | ('repulsiveforce', '1.5'), 23 | ('fontname', c.RENDER_CLOUD_FONT), 24 | ] 25 | edge_attributes = [ 26 | ('color', 'white'), 27 | ('fontname', c.RENDER_CLOUD_FONT), 28 | ] 29 | node_attributes = [ 30 | ('fontname', c.RENDER_CLOUD_FONT), 31 | ] 32 | font_sizes = WordCloudDotRenderer.calculate_font_size(self.graph) 33 | dg = Digraph(comment='Skill Graph', graph_attr=graph_attributes, edge_attr=edge_attributes, node_attr=node_attributes) 34 | for p in self.graph.people.values(): 35 | dg.node(f'P{p.id}', p.name, shape="ellipse", color=c.RENDER_ELLIPSE_COLOR, style='filled') 36 | for s in self.graph.skills.values(): 37 | dg.node(f'S{s.id}', s.name, shape="rectangle", color=c.RENDER_RECTANGLE_COLOR, style='filled', fontsize=str(font_sizes[s.id])) 38 | for p, s in self.graph.people_skills: 39 | dg.edge(f'P{p.id}', f'S{s.id}') 40 | return dg 41 | 42 | class SubGraphDotRenderer(object): 43 | """Renders a partial graph into a graphviz.Digraph object""" 44 | 45 | def __init__(self, graph: model.Graph): 46 | super(SubGraphDotRenderer, self).__init__() 47 | self.graph = graph 48 | 49 | def render(self): 50 | graph_attributes = [ 51 | ('overlap', 'false'), 52 | ('layout', 'sfdp'), 53 | ('ranksep', '3'), 54 | ('bgcolor', 'black'), 55 | ('K', '0.6'), 56 | ('repulsiveforce', '1.5'), 57 | ('fontname', c.RENDER_CLOUD_FONT), 58 | ] 59 | edge_attributes = [ 60 | ('color', 'white'), 61 | ('fontname', c.RENDER_CLOUD_FONT), 62 | ] 63 | node_attributes = [ 64 | ('fontname', c.RENDER_CLOUD_FONT), 65 | ] 66 | dg = Graph(comment='Skill Graph', graph_attr=graph_attributes, edge_attr=edge_attributes, node_attr=node_attributes) 67 | for p in self.graph.people.values(): 68 | dg.node(f'P{p.id}', p.name, shape="ellipse", color=c.RENDER_ELLIPSE_COLOR, style='filled') 69 | for s in self.graph.skills.values(): 70 | dg.node(f'S{s.id}', s.name, shape="rectangle", color=c.RENDER_RECTANGLE_COLOR, style='filled') 71 | for p, s in self.graph.people_skills: 72 | dg.edge(f'P{p.id}', f'S{s.id}') 73 | return dg 74 | 75 | class WordCloudDotRenderer(object): 76 | """Renders a wordcloud into a graphviz.Graph object""" 77 | 78 | def __init__(self, graph: model.Graph): 79 | super(WordCloudDotRenderer, self).__init__() 80 | self.graph = graph 81 | 82 | def render(self): 83 | graph_attributes = [ 84 | ('overlap', 'false'), 85 | ('layout', 'circo'), 86 | ('model', 'subset'), 87 | ('fontname', c.RENDER_CLOUD_FONT), 88 | ] 89 | node_attributes = [ 90 | ('fontname', c.RENDER_CLOUD_FONT), 91 | ] 92 | dg = Graph(comment='Skills Word Cloud', graph_attr=graph_attributes, node_attr=node_attributes) 93 | font_sizes = WordCloudDotRenderer.calculate_font_size(self.graph) 94 | for s in self.graph.skills.values(): 95 | dg.node(f'S{s.id}', s.name, shape='plaintext', fontsize=str(font_sizes[s.id])) 96 | return dg 97 | 98 | @staticmethod 99 | def calculate_font_size(graph: model.Graph, min_font_size: int = 14, max_font_size: int = 36): 100 | font_range = max_font_size - min_font_size 101 | max_people = 0 102 | # Find the skill with the most people 103 | for skill in graph.skills.values(): 104 | max_people = max(max_people, len(skill.people)) 105 | # Create map with skill id and its normalized prominence 106 | font_sizes_map = {} 107 | for skill in graph.skills.values(): 108 | # Value between 0 and 1, non-linear 109 | normalized = (len(skill.people) / max_people)**2 110 | # Font size between min and max relative to how many people 111 | font_size = int(normalized * font_range) + min_font_size 112 | font_sizes_map[skill.id] = font_size 113 | # print(f'{skill.name}: {len(skill.people)} (max: {max_people}) -> {normalized} -> {font_size}') 114 | return font_sizes_map 115 | 116 | class ImageFileRenderer(object): 117 | """Renders a graph into an image file""" 118 | 119 | def __init__(self, dot_graph: Dot): 120 | super(ImageFileRenderer, self).__init__() 121 | self.dot_graph = dot_graph 122 | 123 | def render(self, path_prefix: str = './graph.dot', image_format: str = 'jpg'): 124 | return self.dot_graph.render(format=image_format, filename=path_prefix, view=False) 125 | -------------------------------------------------------------------------------- /repository/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | -------------------------------------------------------------------------------- /repository/core.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | from typing import Tuple 3 | import sqlite3 4 | from os.path import exists as file_exists 5 | import model 6 | 7 | class SqliteRepository(object): 8 | """ 9 | Graph holding a consistent view of skills, people, and their 10 | relationships. Implemented with SQLite3. 11 | """ 12 | 13 | __DB_INIT = [ 14 | ''' 15 | CREATE TABLE skills ( 16 | skill_id INTEGER PRIMARY KEY, 17 | skill_name TEXT NOT NULL 18 | ); 19 | ''', 20 | ''' 21 | CREATE TABLE people ( 22 | person_id INTEGER PRIMARY KEY, 23 | person_name TEXT NOT NULL 24 | ); 25 | ''', 26 | ''' 27 | CREATE TABLE people_skills ( 28 | person_id INTEGER NOT NULL, 29 | skill_id INTEGER NOT NULL, 30 | UNIQUE (person_id, skill_id) 31 | ); 32 | ''', 33 | 'CREATE UNIQUE INDEX idx_skills_id ON skills(skill_id);', 34 | 'CREATE UNIQUE INDEX idx_skills_names ON skills(skill_name);', 35 | 'CREATE UNIQUE INDEX idx_people_id ON people(person_id);', 36 | 'CREATE UNIQUE INDEX idx_people_skills ON people_skills(person_id, skill_id);', 37 | ] 38 | 39 | __CREATE_IF_NOT_EXIST_PERSON = 'INSERT OR IGNORE INTO people VALUES (?, ?);' 40 | __CREATE_IF_NOT_EXIST_SKILL = 'INSERT OR IGNORE INTO skills VALUES (?, ?);' 41 | __ADD_PERSON_SKILL = 'INSERT OR IGNORE INTO people_skills VALUES (?, ?);' 42 | __REMOVE_PERSON_SKILL = 'DELETE FROM people_skills WHERE person_id = ? AND skill_id = ?;' 43 | __SKILL_EXISTS = 'SELECT EXISTS(SELECT 1 FROM skills WHERE skill_id = ?);' 44 | __SELECT_ALL_SKILLS = "SELECT skill_id, skill_name FROM skills;" 45 | __SELECT_ALL_PEOPLE = "SELECT person_id, person_name FROM people;" 46 | __SELECT_ALL_PEOPLE_SKILLS = "SELECT person_id, skill_id FROM people_skills;" 47 | __COUNT_SKILLS = "SELECT COUNT(*) FROM skills;" 48 | __COUNT_PEOPLE = "SELECT COUNT(*) FROM people;" 49 | __COUNT_PEOPLE_SKILLS = "SELECT COUNT(*) FROM people_skills;" 50 | 51 | def __init__(self, database_file): 52 | super(SqliteRepository, self).__init__() 53 | if not file_exists(database_file): 54 | print(f'🌐 Initializing database: {database_file}') 55 | new_db_uri = f'file:{database_file}?mode=rwc' 56 | new_db = sqlite3.connect(new_db_uri, uri=True) 57 | for statement in SqliteRepository.__DB_INIT: 58 | new_db.execute(statement) 59 | new_db.close() 60 | db_uri = f'file:{database_file}?mode=rw' 61 | self.db = sqlite3.connect(db_uri, uri=True) 62 | 63 | def print_stats(self): 64 | print("--") 65 | print("Graph stats: ") 66 | print(" 🙂 People: {}".format(self.get_people_count())) 67 | print(" 🔨 Skills: {}".format(self.get_skills_count())) 68 | print(" ↔️ Connections: {}".format(self.get_people_skills_count())) 69 | print("--") 70 | 71 | def add_skill(self, skill_id: int, skill_name: str): 72 | print(f'🌐 Add skill: {skill_name} (id: {skill_id})') 73 | self.db.execute(SqliteRepository.__CREATE_IF_NOT_EXIST_SKILL, [skill_id, skill_name]) 74 | self.db.commit() 75 | 76 | def add_person(self, person_id: int, person_name: str): 77 | print(f'🌐 Add person: {person_name} (id: {person_id})') 78 | self.db.execute(SqliteRepository.__CREATE_IF_NOT_EXIST_PERSON, [person_id, person_name]) 79 | self.db.commit() 80 | 81 | def add_person_skill(self, person_id: int, skill_id: int): 82 | print(f'🌐 Link person: {person_id} to skill: {skill_id}') 83 | self.db.execute(SqliteRepository.__ADD_PERSON_SKILL, [person_id, skill_id]) 84 | self.db.commit() 85 | 86 | def remove_person_skill(self, person_id: int, skill_id: int): 87 | print(f'🌐 Unlink person: {person_id} to skill: {skill_id}') 88 | self.db.execute(SqliteRepository.__REMOVE_PERSON_SKILL, [person_id, skill_id]) 89 | self.db.commit() 90 | 91 | def skill_exists(self, skill_id: int): 92 | return self.db.execute(SqliteRepository.__SKILL_EXISTS, [skill_id]).fetchone()[0] == 1 93 | 94 | def get_people_count(self) -> int: 95 | return int(self.db.execute(SqliteRepository.__COUNT_PEOPLE).fetchone()[0]) 96 | 97 | def get_skills_count(self) -> int: 98 | return int(self.db.execute(SqliteRepository.__COUNT_SKILLS).fetchone()[0]) 99 | 100 | def get_people_skills_count(self) -> int: 101 | return int(self.db.execute(SqliteRepository.__COUNT_PEOPLE_SKILLS).fetchone()[0]) 102 | 103 | def get_skills(self) -> Iterable[model.Skill]: 104 | return [ 105 | # Construct a new Skill 106 | model.Skill(sid, sname) 107 | # For each row returned 108 | for sid, sname in self.db.execute(SqliteRepository.__SELECT_ALL_SKILLS).fetchall() 109 | ] 110 | 111 | def find_skill(self, skill_name): 112 | row = self.db.execute('SELECT skill_id FROM skills WHERE skill_name = ?', [skill_name]).fetchone() 113 | if row: 114 | return row[0] 115 | else: 116 | return None 117 | 118 | def get_people(self) -> Iterable[model.Person]: 119 | return [ 120 | # Construct a new Person 121 | model.Person(pid, pname) 122 | # For each row returned 123 | for pid, pname in self.db.execute(SqliteRepository.__SELECT_ALL_PEOPLE).fetchall() 124 | ] 125 | 126 | def get_people_skills(self) -> Iterable[Tuple[int, int]]: 127 | return self.db.execute(SqliteRepository.__SELECT_ALL_PEOPLE_SKILLS).fetchall() 128 | 129 | def find_people_by_id(self, people_ids: Iterable[int]) -> Iterable[model.Person]: 130 | ids = ','.join([str(id) for id in people_ids]) 131 | query = f"SELECT person_id, person_name FROM people WHERE person_id IN ({ids});" 132 | return [ 133 | # Construct a new Person 134 | model.Person(pid, pname) 135 | # For each row returned 136 | for pid, pname in self.db.execute(query).fetchall() 137 | ] 138 | 139 | def find_skills_by_id(self, skills_ids: Iterable[int]) -> Iterable[model.Skill]: 140 | ids = ','.join([str(id) for id in skills_ids]) 141 | query = f"SELECT skill_id, skill_name FROM skills WHERE skill_id IN ({ids});" 142 | return [ 143 | # Construct a new Skill 144 | model.Skill(sid, sname) 145 | # For each row returned 146 | for sid, sname in self.db.execute(query).fetchall() 147 | ] 148 | 149 | def find_skills_by_name(self, skill_name: str) -> Iterable[model.Skill]: 150 | wildcard_name = f'%{skill_name}%' 151 | return [ 152 | # Construct a new Skill 153 | model.Skill(sid, sname) 154 | # For each row returned 155 | for sid, sname in self.db.execute('SELECT skill_id, skill_name FROM skills WHERE skill_name LIKE ?', [wildcard_name]).fetchall() 156 | ] 157 | 158 | def find_people_skills_of_people(self, people_ids: Iterable[int]) -> Iterable[Tuple[int, int]]: 159 | ids = ','.join([str(id) for id in people_ids]) 160 | query = f"SELECT person_id, skill_id FROM people_skills WHERE person_id IN ({ids});" 161 | return self.db.execute(query).fetchall() 162 | 163 | def find_people_skills_of_skills(self, skills_ids: Iterable[int]) -> Iterable[Tuple[int, int]]: 164 | ids = ','.join([str(id) for id in skills_ids]) 165 | query = f"SELECT person_id, skill_id FROM people_skills WHERE skill_id IN ({ids});" 166 | return self.db.execute(query).fetchall() 167 | 168 | def get_graph_snapshot(self) -> model.Graph: 169 | g = model.Graph() 170 | # Capture edges first, to avoid dangling references 171 | people_skills = self.get_people_skills() 172 | for p in self.get_people(): 173 | g.add_person(p) 174 | for s in self.get_skills(): 175 | g.add_skill(s) 176 | for pid, sid in people_skills: 177 | g.link_person_to_skill(pid, sid) 178 | return g 179 | 180 | def get_people_subgraph_snapshot(self, people_ids: Iterable[int]) -> model.Graph: 181 | g = model.Graph() 182 | # Capture edges first, to avoid dangling references 183 | people_skills = self.find_people_skills_of_people(people_ids) 184 | skill_ids = set([sid for pid, sid in people_skills]) 185 | for p in self.find_people_by_id(people_ids): 186 | g.add_person(p) 187 | for s in self.find_skills_by_id(skill_ids): 188 | g.add_skill(s) 189 | for pid, sid in people_skills: 190 | g.link_person_to_skill(pid, sid) 191 | return g 192 | 193 | def get_skills_subgraph_snapshot(self, skill_ids: Iterable[int]) -> model.Graph: 194 | g = model.Graph() 195 | # Capture edges first, to avoid dangling references 196 | people_skills = self.find_people_skills_of_skills(skill_ids) 197 | people_ids = set([pid for pid, sid in people_skills]) 198 | for p in self.find_people_by_id(people_ids): 199 | g.add_person(p) 200 | for s in self.find_skills_by_id(skill_ids): 201 | g.add_skill(s) 202 | for pid, sid in people_skills: 203 | g.link_person_to_skill(pid, sid) 204 | return g 205 | -------------------------------------------------------------------------------- /requirements.system: -------------------------------------------------------------------------------- 1 | graphviz 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | discord.py 2 | python-dotenv 3 | graphviz 4 | -------------------------------------------------------------------------------- /sample/full-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/full-graph.jpg -------------------------------------------------------------------------------- /sample/interest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/interest.jpg -------------------------------------------------------------------------------- /sample/metagame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/metagame.jpg -------------------------------------------------------------------------------- /sample/overlap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/overlap.jpg -------------------------------------------------------------------------------- /sample/self.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/self.jpg -------------------------------------------------------------------------------- /sample/skill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/skill.jpg -------------------------------------------------------------------------------- /sample/word-cloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaFam/skill-bot/8657c2b6e8db13e88472411218c8f77bf23e66f5/sample/word-cloud.jpg -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | 2 | def print_graph(g): 3 | print(f"People: {len(g.people)}") 4 | for pid, p in g.people.items(): 5 | print(f" * {p.name} ({p.id})") 6 | for s in p.skills: 7 | print(f" - {s.name} ({s.id})") 8 | 9 | print(f"Skills: {len(g.skills)}") 10 | for sid, s in g.skills.items(): 11 | print(f" * {s.name} ({s.id})") 12 | for p in s.people: 13 | print(f" - {p.name} ({p.id})") 14 | 15 | print(f"People skills: {len(g.people_skills)}") 16 | for person, skill in g.people_skills: 17 | print(f"{person.name} <-> {skill.name}") 18 | 19 | ############################# 20 | # Model tests 21 | ############################# 22 | 23 | import model 24 | 25 | p1 = model.Person(1, "Alice") 26 | p2 = model.Person(2, "Bob") 27 | p3 = model.Person(3, "Charles") 28 | 29 | s1 = model.Skill(11, "Dancing") 30 | s2 = model.Skill(22, "Fishing") 31 | 32 | g = model.Graph() 33 | 34 | g.add_person(p1) 35 | g.add_person(p2) 36 | g.add_person(p3) 37 | 38 | g.add_skill(s1) 39 | g.add_skill(s2) 40 | 41 | g.link_person_to_skill(1, 11) 42 | g.link_person_to_skill(1, 22) 43 | g.link_person_to_skill(2, 11) 44 | g.link_person_to_skill(3, 22) 45 | 46 | assert g.people[p1.id] == p1 47 | assert g.people[p2.id] == p2 48 | assert g.people[p3.id] == p3 49 | assert g.skills[s1.id] == s1 50 | assert g.skills[s2.id] == s2 51 | 52 | assert p1 in s1.people 53 | assert p1 in s2.people 54 | assert p2 in s1.people 55 | assert p3 in s2.people 56 | 57 | assert s1 in p1.skills 58 | assert s2 in p1.skills 59 | assert s1 in p2.skills 60 | assert s2 in p3.skills 61 | 62 | assert len(g.people_skills) == 4 63 | 64 | for s in [s1, s2]: 65 | for p in s.people: 66 | assert (p, s) in g.people_skills 67 | 68 | for p in [p1, p2, p3]: 69 | for s in p.skills: 70 | assert (p, s) in g.people_skills 71 | 72 | print_graph(g) 73 | 74 | ############################# 75 | # Repository tests 76 | ############################# 77 | 78 | import os 79 | import repository 80 | 81 | TEST_DB_FILE = 'test-db.sqlite' 82 | 83 | if os.path.exists(TEST_DB_FILE): 84 | os.remove(TEST_DB_FILE) 85 | 86 | # Create and initialize new DB 87 | tmp_repo = repository.SqliteRepository(TEST_DB_FILE) 88 | 89 | # Open exsisting DB 90 | repo = repository.SqliteRepository(TEST_DB_FILE) 91 | 92 | # Test all tables are empty 93 | assert repo.get_skills_count() == 0 94 | assert repo.get_people_count() == 0 95 | assert repo.get_people_skills_count() == 0 96 | assert repo.get_skills()[:] == [] 97 | assert repo.get_people()[:] == [] 98 | assert repo.get_people_skills()[:] == [] 99 | assert repo.find_people_by_id([1, 2, 3, 4]) == [] 100 | assert repo.find_skills_by_id([1, 2, 3, 4]) == [] 101 | assert repo.find_people_skills_of_people([1, 2, 3, 4]) == [] 102 | assert repo.find_people_skills_of_skills([1, 2, 3, 4]) == [] 103 | assert repo.find_skills_by_name("") == [] 104 | 105 | # Add 3 skills 106 | repo.add_skill(111, "Hunting") 107 | repo.add_skill(222, "Fishing") 108 | repo.add_skill(333, "Farming") 109 | 110 | # Test skills 111 | assert repo.get_skills_count() == 3 112 | assert repo.skill_exists(111) 113 | assert repo.skill_exists(222) 114 | assert repo.skill_exists(333) 115 | assert not repo.skill_exists(444) 116 | assert repo.find_skill("Hunting") == 111 117 | assert repo.find_skill("Fishing") == 222 118 | assert repo.find_skill("Farming") == 333 119 | assert repo.find_skill("Dancing") == None 120 | assert len(repo.find_skills_by_id([111, 222, 333, 555])) == 3 121 | assert len(repo.find_skills_by_name("")) == 3 122 | assert len(repo.find_skills_by_name("ing")) == 3 123 | assert len(repo.find_skills_by_name("FISH")) == 1 124 | 125 | skills = repo.get_skills() 126 | assert len(skills) == 3 127 | for s in skills: 128 | if s.id == 111: 129 | assert s.name == "Hunting" 130 | elif s.id == 222: 131 | assert s.name == "Fishing" 132 | elif s.id == 333: 133 | assert s.name == "Farming" 134 | else: 135 | assert False 136 | 137 | # Test overwrite skill name (no effect, and no duplicates) 138 | repo.add_skill(222, "Phishing") 139 | assert repo.get_skills_count() == 3 140 | skills = repo.get_skills() 141 | assert len(skills) == 3 142 | for s in skills: 143 | if s.id == 111: 144 | assert s.name == "Hunting" 145 | elif s.id == 222: 146 | assert s.name == "Fishing" 147 | elif s.id == 333: 148 | assert s.name == "Farming" 149 | else: 150 | assert False 151 | 152 | # Add 2 people 153 | repo.add_person(1, "Joe") 154 | repo.add_person(2, "Sammy") 155 | 156 | # Test people 157 | assert repo.get_people_count() == 2 158 | people = repo.get_people() 159 | assert len(people) == 2 160 | assert [p for p in people if p.id == 1][0].name == "Joe" 161 | assert [p for p in people if p.id == 2][0].name == "Sammy" 162 | assert len(repo.find_people_by_id([1, 2, 3, 4])) == 2 163 | 164 | # Test overwrite person name (no effect, and no duplicates) 165 | repo.add_person(1, "Joe Reloaded") 166 | assert repo.get_people_count() == 2 167 | people = repo.get_people() 168 | assert len(people) == 2 169 | assert [p for p in people if p.id == 1][0].name == "Joe" 170 | assert [p for p in people if p.id == 2][0].name == "Sammy" 171 | 172 | # Add 4 people-skills relationships 173 | repo.add_person_skill(1, 111) 174 | repo.add_person_skill(2, 111) 175 | repo.add_person_skill(1, 222) 176 | repo.add_person_skill(2, 333) 177 | 178 | # Test people skills 179 | assert repo.get_people_skills_count() == 4 180 | people_skills = repo.get_people_skills() 181 | assert len(people_skills) == 4 182 | assert (1, 111) in people_skills 183 | assert (2, 111) in people_skills 184 | assert (1, 222) in people_skills 185 | assert (2, 333) in people_skills 186 | assert len(repo.find_people_skills_of_people([1, 2])) == 4 187 | assert len(repo.find_people_skills_of_people([1, 999])) == 2 188 | assert len(repo.find_people_skills_of_people([2])) == 2 189 | assert len(repo.find_people_skills_of_skills([111])) == 2 190 | assert len(repo.find_people_skills_of_skills([111, 999])) == 2 191 | assert len(repo.find_people_skills_of_skills([111, 222, 333])) == 4 192 | 193 | # Remove one person skill 194 | repo.remove_person_skill(1, 222) 195 | 196 | # Test people skills 197 | assert repo.get_people_skills_count() == 3 198 | people_skills = repo.get_people_skills() 199 | assert len(people_skills) == 3 200 | assert (1, 111) in people_skills 201 | assert (2, 111) in people_skills 202 | assert not (1, 222) in people_skills 203 | assert (2, 333) in people_skills 204 | assert len(repo.find_people_skills_of_people([1, 2])) == 3 205 | assert len(repo.find_people_skills_of_people([1, 999])) == 1 206 | assert len(repo.find_people_skills_of_people([2])) == 2 207 | assert len(repo.find_people_skills_of_skills([111])) == 2 208 | assert len(repo.find_people_skills_of_skills([111, 999])) == 2 209 | assert len(repo.find_people_skills_of_skills([111, 222, 333])) == 3 210 | 211 | # Remove non-existent person skill 212 | repo.remove_person_skill(1, 222) 213 | 214 | # Test people skills 215 | people_skills = repo.get_people_skills() 216 | assert len(people_skills) == 3 217 | assert (1, 111) in people_skills 218 | assert (2, 111) in people_skills 219 | assert (2, 333) in people_skills 220 | 221 | # Add duplicate people-skills relationships 222 | repo.add_person_skill(1, 111) 223 | repo.add_person_skill(1, 111) 224 | repo.add_person_skill(1, 111) 225 | 226 | # Test people skills 227 | people_skills = repo.get_people_skills() 228 | assert len(people_skills) == 3 229 | assert (1, 111) in people_skills 230 | assert (2, 111) in people_skills 231 | assert (2, 333) in people_skills 232 | 233 | ############################# 234 | # Repository + model test 235 | ############################# 236 | 237 | g = repo.get_graph_snapshot() 238 | 239 | print_graph(g) 240 | 241 | assert len(g.people) == 2 242 | assert len(g.skills) == 3 243 | assert len(g.people_skills) == 3 244 | 245 | # Subgraph filtered by people 246 | people_subgraph = repo.get_people_subgraph_snapshot([2, 9999]) 247 | print_graph(people_subgraph) 248 | assert len(people_subgraph.people) == 1 249 | assert len(people_subgraph.skills) == 2 250 | assert len(people_subgraph.people_skills) == 2 251 | 252 | # Subgraph filtered by skills 253 | skill_ids = set() 254 | for skill_name in ['fish', 'farm']: 255 | for skill in repo.find_skills_by_name(skill_name): 256 | skill_ids.add(skill.id) 257 | skills_subgraph = repo.get_skills_subgraph_snapshot(skill_ids) 258 | print_graph(skills_subgraph) 259 | assert len(skills_subgraph.people) == 1 260 | assert len(skills_subgraph.skills) == 2 261 | assert len(skills_subgraph.people_skills) == 1 262 | 263 | ############################# 264 | # Model rendering test 265 | ############################# 266 | alice = model.Person(1, "Alice") 267 | bob = model.Person(2, "Bob") 268 | charles = model.Person(3, "Charles") 269 | diana = model.Person(4, "Diana") 270 | 271 | cooking = model.Skill(11, "Cooking") 272 | gardening = model.Skill(22, "Gardening") 273 | fishing = model.Skill(33, "Fishing") 274 | hunting = model.Skill(44, "Hunting") 275 | woodworking = model.Skill(55, "Woodworking") 276 | 277 | g = model.Graph() 278 | 279 | g.add_person(alice) 280 | g.add_person(bob) 281 | g.add_person(charles) 282 | g.add_person(diana) 283 | 284 | g.add_skill(cooking) 285 | g.add_skill(gardening) 286 | g.add_skill(fishing) 287 | g.add_skill(hunting) 288 | g.add_skill(woodworking) 289 | 290 | g.link_person_to_skill(alice.id, cooking.id) 291 | g.link_person_to_skill(alice.id, fishing.id) 292 | g.link_person_to_skill(alice.id, hunting.id) 293 | g.link_person_to_skill(bob.id, woodworking.id) 294 | g.link_person_to_skill(charles.id, cooking.id) 295 | g.link_person_to_skill(charles.id, fishing.id) 296 | g.link_person_to_skill(diana.id, cooking.id) 297 | g.link_person_to_skill(diana.id, gardening.id) 298 | 299 | import os 300 | from render import * 301 | 302 | dot_full_graph = FullGraphDotRenderer(g).render() 303 | png_file = ImageFileRenderer(dot_full_graph).render(path_prefix="test-full.dot") 304 | assert png_file == "test-full.dot.jpg" 305 | assert os.path.exists(png_file) 306 | 307 | dot_word_cloud_graph = WordCloudDotRenderer(g).render() 308 | png_file = ImageFileRenderer(dot_word_cloud_graph).render(path_prefix="test-word-cloud.dot") 309 | assert png_file == "test-word-cloud.dot.jpg" 310 | assert os.path.exists(png_file) 311 | 312 | dot_graph_sub_people = SubGraphDotRenderer(people_subgraph).render() 313 | png_file = ImageFileRenderer(dot_graph_sub_people).render(path_prefix="test-sub-people.dot") 314 | assert png_file == "test-sub-people.dot.jpg" 315 | assert os.path.exists(png_file) 316 | 317 | dot_graph_sub_skills = SubGraphDotRenderer(skills_subgraph).render() 318 | png_file = ImageFileRenderer(dot_graph_sub_skills).render(path_prefix="test-sub-skills.dot") 319 | assert png_file == "test-sub-skills.dot.jpg" 320 | assert os.path.exists(png_file) 321 | -------------------------------------------------------------------------------- /tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | --------------------------------------------------------------------------------