Merge branch 'modules-as-actions' into 'master'

Merge modules-as-action refactor to main!

See merge request medlabboulder/modpol!31
This commit is contained in:
Nathan Schneider 2021-12-20 06:27:42 +00:00
commit 6aa09f9fdd
50 changed files with 1272 additions and 1082 deletions

View File

@ -6,6 +6,7 @@ One administrator, @ntnsndr, holds ultimate decision-making power over the proje
* **Do-ocracy** Those who step forward to do a given task can decide how it should be done, in ongoing consultation with each other and the administrator.
* **Membership** Participation is open to anyone who wants to contribute. The administrator can remove misbehaving participants at will for the sake of the common good.
* **Code of Conduct** Participants are expected to abide by the Contributor Covenant (contributor-covenant.org).
* **Ownership** This is a project of the Media Enterprise Design Lab at the University of Colorado Boulder and is owned by the university.
---

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Media Enterprise Design Lab, University of Colorado Boulder
Copyright (c) 2021 University of Colorado Boulder. A project of the Media Enterprise Design Lab (colorado.edu/lab/medlab/).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,54 +1,81 @@
# Modular Politics Prototype for Minetest
# Modular Politics for Minetest
This is a mod for [Minetest](https://minetest.net) that enables diverse governance mechanisms. It seeks to implement the [Modular Politics](https://metagov.org/modpol) proposal. In the future, it
will be possible to use this framework to simulate governance in a
number of platform contexts.
Modular Politics is an extension that enables diverse governance processes on multi-user platforms. It offers a library that enables users to create or adapt their own modules that add specific governance functionalities.
This mod produces an API that can serve as a dependency for other mods that add specific governance functionalities.
This implementation is a mod for [Minetest](https://minetest.net), a free/open-source voxel game. It is designed to be easily adapted to other multi-user platforms that also employ Lua as an extension language.
For background information and project roadmap, see [the wiki](https://gitlab.com/medlabboulder/modpol/-/wikis/home).
## Design philosophy
Modular Politics seeks to implement a theoretical framework, also called "[modular politics](https://metagov.org/modpol)," which proposes these design goals:
* *Modularity*: Platform operators and community members should have the ability to construct systems by creating, importing, and arranging composable parts together as a coherent whole.
* *Expressiveness*: The governance layer should be able to implement as wide a range of processes as possible.
* *Portability*: Governance tools developed for one platform should be portable to another platform for reuse and adaptation.
* *Interoperability*: Governance systems operating on different platforms and protocols should have the ability to interact with each other, sharing data and influencing each other's processes.
Additionally, Modular Politics seeks to counteract the tendency for "[implicit feudalism](https://ntnsndr.in/ImplicitFeudalism)," according to which rigid, top-down power structures are the norm in online spaces. To this end, some design patterns include:
* *Groups, not roles*: While most platforms assign powers through particular permissions given to individuals, in Modular Politics, power lies in groups (which Modular Politics calls "orgs").
* *Consent, not oligarchy*: Rather than assuming that decisions will be made by a few power-holders, the software assumes that consent by all affected users is the norm.
* *Inheritance, not blank slates*: When a new group is formed, it inherits the patterns of what preceded it, rather than imagining that it is starting from scratch.
It is certainly possible to use Modular Politics to replicate practices of implicit feudalism, such as all-powerful admins, but doing so requires extra work to overcome these defaults.
## Installation in Minetest
To use this in Minetest, simply install it in your mods/ or worldmods/ folder. Minetest will load init.lua.
To use this in Minetest, simply install it in your `mods/` or `worldmods/` folder. Minetest will load `init.lua`.
In the game, open the Modular Politics interface with the command `/modpol`.
In the game, open the Modular Politics dashboard with the command `/mp`.
For testing purposes, players with the `privs` privilege (generally admins) can use the `/mp` command, which resets all the orgs and opens a dashboard.
## Standalone Version on the Command Line
Modular Politics can also be used independently of Minetest as a command-line tool. Currently command-line use of modpol requires a Unix-style system, but it is intended to become more fully platform independent.
The command-line version is in the `modpol` subdirectory. To interact with the interpreter on Unix systems in CLI mode, install lua or luajit and execute the following command in this directory:
The command-line version is in the `modpol` subdirectory. To run the program on Unix systems in CLI mode, install lua or luajit and execute the following in this directory:
```
$ cd modpol/
$ lua [or luajit]
> dofile("modpol.lua")
$ lua[jit] login.lua
```
For a list of global functions and tables, use `modpol.menu()`.
You can also interact with the interpreter by starting it this way:
```
$ lua[jit]
> dofile("login.lua")
```
In the interpreter, for a list of global functions and tables, use `modpol.menu()`.
## Storage
By default, a data directory named "data" will be created in this directory. "/data" will contain a log file and serialized program data files.
By default, a data directory named "data" will be created in this directory. `/data` will contain a log file and serialized program data files.
Another storage method may be chosen in modpol.lua. A StorageRef-based method for Minetest 5.* is included: storage-mod_storage.lua.
Another storage method may be chosen in `modpol.lua`. A StorageRef-based method for Minetest 5.* is included: `storage-mod_storage.lua`.
## Credits
Initiated by [Nathan Schneider](https://nathanschneider.info) of the [Media Enterprise Design Lab](https://colorado.edu/lab/medlab) at the University of Colorado Boulder, as part of the [Metagovernance Project](https://metagov.org). Based on the paper "[Modular Politics: Toward a Governance Layer for Online Communities](https://metagov.org/modpol)."
This project is led by [Nathan Schneider](https://nathanschneider.info) of the [Media Enterprise Design Lab](https://colorado.edu/lab/medlab) at the University of Colorado Boulder, as part of the [Metagovernance Project](https://metagov.org).
Other contributors include:
Contributors include:
* [Luke Miller](https://gitlab.com/lukvmil) (main control flow, object orientation, module spec)
* [MisterE](https://gitlab.com/gbrrudmin) (project refactoring, core feature development)
* [Luke Miller](https://gitlab.com/lukvmil) (co-leadership, main control flow, object orientation, module spec)
* [MisterE](https://gitlab.com/gbrrudmin) (early project refactoring, core feature development)
* Robert Kiraly [[OldCoder](https://github.com/oldcoder/)] (ocutils.lua, storage-local.lua, project refactoring)
* Skylar Hew (documentation)
We'd love to welcome more contributors, particularly from the Minetest community! Please join the conversation in the [Issues](https://gitlab.com/medlabboulder/modpol/-/issues) or the [Minetest.net forum](https://forum.minetest.net/viewtopic.php?f=47&t=26037).
We are grateful for initial support for this project from a residency with [The Bentway Conservancy](https://www.thebentway.ca/). Read about us in _[The Field Guide to Digital and/as Public Space](https://www.thebentway.ca/stories/field-guide/)_.
## Contributing
We'd love to welcome more contributors. Please join the conversation in the [Issues](https://gitlab.com/medlabboulder/modpol/-/issues), our [Matrix.org channel](https://matrix.to/#/#minetest-modpol:matrix.org), and the [Minetest.net forum](https://forum.minetest.net/viewtopic.php?f=47&t=26037).
Learn more about the project and how to develop your own modules in [the wiki](https://gitlab.com/medlabboulder/modpol/-/wikis/home).
We are grateful for support for this project from a residency with [The Bentway Conservancy](https://www.thebentway.ca/).
## Licenses

View File

@ -1 +1 @@
Provides an API for diverse governance mechanisms
Provides a basis for diverse, user-configurable modules for in-game governance.

View File

@ -14,7 +14,7 @@ modpol = {}
-- get modpol_minetest's version of directory
modpol.get_script_dir = function()
return minetest.get_modpath("modpol").."/modpol"
return minetest.get_modpath("modpol").."/modpol_core"
end
-- TKTK: Implement minetest settingtypes for this... for now, the default is to use minetest mod storage.
@ -34,7 +34,7 @@ modpol.storage_file_path = minetest.get_modpath("modpol").."/modpol_minetest/sto
-- ===================================================================
-- Load modpol system
dofile(minetest.get_modpath("modpol") .. "/modpol/modpol.lua")
dofile(minetest.get_modpath("modpol") .. "/modpol_core/modpol.lua")
-- ===================================================================

7
login.lua Normal file
View File

@ -0,0 +1,7 @@
dofile("modpol_core/modpol.lua")
print("Log in as which user?")
local username = io.read()
print()
modpol.interactions.dashboard(username)

View File

@ -1,35 +0,0 @@
When calling modpol.lua from another file, one may wish to use
different functions than those included by default.
So far, 2 definitions need to be made BEFORE calling modpol.lua, the rest can be overwritten
after calling modpol.lua .
setup:
----------------
Define the global modpol table:
modpol = {}
the following 2 items *may* be defined, if they are not, then defaults will be used:
========================
--------------------
modpol.get_script_dir()
--------------------
type: function
defaults to: a function that can only work in linux
usage: must return the file path of the modpol directory. If modpol is in a
larger directory, such as a mod filesystem, make sure that the path returned is the "base"
modpol folder, i.e. the folder with all the original modpol files.
========================
--------------------
modpol.storage_file_path
--------------------
type: string
defaults to: topdir .. "/storage/storage-local.lua",
where topdir is the directory defined by modpol.storage_file_path
usage: if you wish to use another method of storage than that provided by default,
then you can save a filepath to the storage script here.

View File

@ -1,50 +0,0 @@
Ledger
**************************
a legder is a table of records that holds the history of an org, and of modpol itself.
its structure is a table of legder entries.
Every org has one, starting with the entry recording its creation.
Ledger Entry
**************************
a legder entry is a table with the following properties:
timestamp = nil,
entrytype = nil,
action_msg = '',
the timestamp will hold the output of os.time at the moment the entry was created.
the entrytype is optional; it can hold an identifier of the kind of entry it is (e.g. 'org_init', 'new_user', etc etc.)
action_msg is the record output itself.
Entrytypes
**************************
These are the entrytypes that modpol uses. Each additional module should document whatever entrytypes it adds.
'org_init'
type used when an org is created
'org_purge'
type used when all orgs are purged. -- will only be found in the main modpol ledger.
'org_remove'
type used when removing an individual org
'add_member'
type used when adding a member to an org
'remove_member'
type used when removing a member from an org
Old orgs
**************************
when an org is removed, its ledger is stored in modpol.old_legders = {} Note that multiple ledgers may exist here with the same org name.

View File

@ -1,137 +0,0 @@
== Dev note ===
*note, see doc ledgers for info about org ledgers.
they are *not* just a message in a table anymore... they have a timestamp and more.
===============
Orgs are stored in modpol.orgs by the key 'org_id', a unique integer
Old ledgers of deleted orgs are stored in modpol.old_ledgers,
if preserve records is enabled
An org is a table with the following properties:
{
name = '', -- a unique name...
this is a reference to the key used to store the org table. Changing this does not change the org name.
policies = {}, -- a table to store the org's policies
members = {}, -- a table that contains the member names
or the org, in no particular order (TKTK: maybe change it so that members have a name:properties table?)
ledger = {}, -- a table of ledger entries. See the ledger docs
parent = nil, -- the name of the org that spawned this org.
removing a parent org removes its children
children = {}, -- a table of strings of org names of child orgs
properties = { -- good for modules to store arbitrary info about the org here.
},
}
API functions for orgs
======================
a local variable setting 'preserve_records' determines whether
to store old ledgers when an org is deleted. It is set to true.
It would be possible to extend this to a settings file later.
--==oo888888888888888888888888888888oo==--
Function: modpol.record(org_id, msg, type)
-- Params: strings msg, type, org_id (number)
-- Outputs:
-- "msg" specifies an event and/or status message.
-- "org" specifies an "org" name.
-- "type" -- optional type of legder entry. Could be e.g. 'election_result', 'add_member', etc
-- This function adds the message to a global ledger and, if "org"
-- specifies a valid "org", to an "org"-specific ledger. Both the mem-
-- ory-resident and on-disk copies of the data structures used are up-
-- dated.
-- the type input is used to be able to search ledgers for specific events
--==oo888888888888888888888888888888oo==--
Function: modpol.add_org(org_id, members, parent, properties)
-- Params: org)id (number), table members, string parent, table properties
-- Parent must be an existing org, defaults to 'instance', properties
-- are arbitrary properties to initialize the org with
-- Output:
-- This function creates an "org". It returns a boolean success indicator and
-- either an error message starting with "Error:" or a success message.
--
--
-- The string parameter specifies the "org" name.
--
-- The members table should be an integer-indexed array of member
-- names.
-- parent should be nil or a valid existing org name. If nil, defaults to
-- 'instance'
-- properties defaults to an empty table. Use it to initialize the org
-- with arbitrary properties
--==oo888888888888888888888888888888oo==--
-- Function: modpol.remove_org(org_id, reason)
-- Params: org_id (number), opt string reason
-- Output:
--
-- This function removes an "org". It returns a boolean
-- success indicator and either an error message
-- starting with "Error:" or a success message. It also recursively
-- removes all child orgs, with 'remove parent org' as reason. If
-- preserve_records is enabled, it logs the removal in the org ledger,
-- and stores the ledger in modpol.old_legders
--
-- The string parameter specifies the "org" name.
--
-- The reason is an optional string to additionally include in the
-- log as reason for removal
--==oo888888888888888888888888888888oo==--
Function: modpol.list_orgs()
-- Params: None
-- Output:
-- This function returns a text-format list of "orgs". The list shows
-- one "org" per line in the following format:
-- org_name (member, member, ...)
-- If there are no "orgs", the output is an empty string.
--==oo888888888888888888888888888888oo==--
Function: modpol.reset_orgs()
-- Params: None
-- Removes all orgs and recreates blank org "instance" with all
-- current users
-- returns confirmation message
-- if preserve_records is enabled, stores all org ledgers
-- in modpol.old_ledgers
--==oo888888888888888888888888888888oo==--
Function: modpol.add_member(org_id, member)
-- Params: org_id (number), member (string)
-- Output:
-- Adds the specified member to the specified org
-- Returns a boolean success indicator and
-- either a confirmation or error message
--==oo888888888888888888888888888888oo==--
Function: modpol.is_member
-- Params: org (number), member (string)
-- Output: boolean, or nil and error message if not applicable. (org nonexistent)
--==oo888888888888888888888888888888oo==--
Function: modpol.remove_member
-- Params: org (number), member (string)
-- Output:
-- Removes the specified member from the specified org
-- Returns confirmation or error message

View File

@ -1,16 +0,0 @@
--call all files in this directory
local localdir = modpol.topdir
--Users
dofile (localdir .. "/users/users.lua")
--orgs
dofile (localdir .. "/orgs/base.lua")
dofile (localdir .. "/orgs/requests.lua")
--interactions
dofile (localdir .. "/interactions/interactions.lua")
--modules
dofile (localdir .. "/modules/consent.lua")

View File

@ -1,7 +0,0 @@
dofile("../modpol.lua")
print("Log in as which user?")
local username = io.read()
print()
modpol.interactions.dashboard(username)

View File

@ -1,98 +0,0 @@
modpol.modules = modpol.modules or {}
modpol.modules.consent = {}
-- sets consent to its own callback
modpol.modules.consent.__index = modpol.modules.consent
function temp_consent_process()
return {
type = "consent",
id = nil,
org_id = nil,
request_id = nil,
total_votes = 0,
majority_to_pass = 0.51,
votes_needed = nil,
votes_yes = {},
votes_no = {}
}
end
-- ===============================================
-- function to create a new consent process to resolve a pending process
function modpol.modules.consent:new_process(id, request_id, org_id)
local process = temp_consent_process()
process.id = id
process.request_id = request_id
process.org_id = org_id
setmetatable(process, modpol.modules.consent)
modpol.ocutil.log('Created new process #' .. id .. ' for request id #' .. request_id)
local p_org = modpol.orgs.get_org(org_id)
for i, member in ipairs(p_org.members) do
p_org:add_pending_action(id, member)
end
process.votes_needed = math.ceil(process.majority_to_pass * p_org:get_member_count())
return process
end
-- ============================
-- interact function for the consent module
-- input: user (string)
function modpol.modules.consent:interact(user)
-- TODO this needs more context on the vote at hand
modpol.interactions.binary_poll_user(
user, "Do you consent?",
function(vote)
if vote == 'Yes' then
self:approve(user, true)
elseif vote == 'No' then
self:approve(user, false)
end
end)
end
-- ======================================================
-- function for users to vote on a pending request
function modpol.modules.consent:approve(user, decision)
if not modpol.orgs.get_org(self.org_id):has_member(user) then
modpol.ocutil.log('Error in consent:approve -> user not a member of the org')
return
end
if decision then
table.insert(self.votes_yes, user)
modpol.ocutil.log('User ' .. user .. ' voted yes on request #' .. self.request_id)
else
table.insert(self.votes_no, user)
modpol.ocutil.log('User ' .. user .. ' voted no on request #' .. self.request_id)
end
self.total_votes = self.total_votes + 1
local p_org = modpol.orgs.get_org(self.org_id)
p_org:remove_pending_action(self.id, user)
self:update_status()
end
-- ===================================================
-- determines whether process has finished and resolves request if it has (unfinished)
function modpol.modules.consent:update_status()
local process_org = modpol.orgs.get_org(self.org_id)
if #self.votes_yes >= self.votes_needed then
modpol.ocutil.log('Request #' .. self.request_id .. ' passes')
process_org:resolve_request(self.request_id, true)
elseif #self.votes_no >= self.votes_needed then
modpol.ocutil.log('Request #' .. self.request_id .. ' fails to pass')
process_org:resolve_request(self.request_id, false)
else
modpol.ocutil.log('Waiting for more votes...')
end
end

View File

@ -1,272 +0,0 @@
modpol.orgs.request_params = {
add_org = 1,
delete = 0,
add_member = 1,
remove_member = 1
}
-- ================================
-- creates a new process linked to a request id
function modpol.orgs:create_process(process_type, request_id)
if not modpol.modules[process_type] then
modpol.ocutil.log('Process type "' .. process_type .. '" does not exist')
return
end
local empty_index = nil
-- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.processes) do
if v == 'deleted' then
empty_index = k
break
end
end
local index
-- attempts to fill empty spots in list, otherwise appends to end
if empty_index then
index = empty_index
else
index = #self.processes + 1
end
-- retrieving requested module
local module = modpol.modules[process_type]
local new_process = module:new_process(index, request_id, self.id)
self.processes[index] = new_process
modpol.ocutil.log('Created process #' .. index .. ' - ' .. process_type .. ' for ' .. self.name)
self:record('Created process #' .. index .. ' - ' .. process_type, 'add_process')
return index
end
-- ===========================
-- adds a new pending action to the org's table
function modpol.orgs:add_pending_action(process_id, user)
-- adds tables if they don't exist already
self.pending[user] = self.pending[user] or {}
-- flagging actual action
self.pending[user][process_id] = true
local message =
modpol.interactions.message(user, "New pending action in " .. self.name)
modpol.ocutil.log("Added pending action to " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
self:record('Added pending action to ' .. user .. ' in process #' .. process_id, "add_pending_action")
end
-- ========================
-- removes a pending action from the org's table
function modpol.orgs:remove_pending_action(process_id, user)
if self.pending[user] then
self.pending[user][process_id] = nil
modpol.ocutil.log("Removed pending action from " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
self:record('Removed pending action from ' .. user .. ' in process #' .. process_id, "del_pending_action")
else
modpol.ocutil.log("Could not remove pending action from " .. user .. " in process #" .. process_id)
end
end
-- =====================
-- removes all pending actions for a given process id from all users
function modpol.orgs:wipe_pending_actions(process_id)
for user in pairs(self.pending) do
self.pending[user][process_id] = nil
end
modpol.ocutil.log("Removed all pending actions for process #" .. process_id)
self:record('Removed all pending actions for process #' .. process_id, "del_pending_action")
end
-- ======================
-- returns a boolean to indicate whether a user has any active pending actions
function modpol.orgs:has_pending_actions(user)
-- next() will return the next pair in a table
-- if next() returns nil, the table is empty
if not self.pending[user] then
return false
else
if not next(self.pending[user]) then
return false
else
return true
end
end
end
-- ===========================
-- compares to requests to see if they are identical
function modpol.orgs.comp_req(req1, req2)
-- compares request type
if req1.type ~= req2.type then
return false
else
-- comparing parameters
-- we can assume the number of params is the same as this is checked in the make_request func
for k, v in ipairs(req1.params) do
if v ~= req2.params[k] then
return false
end
end
end
return true
end
-- ===============================
-- returns string of all active requests
function modpol.orgs:list_request()
local str
for id, req in ipairs(self.requests) do
if req ~= "deleted" then
if str then
str = str .. '\n' .. req.type .. ' (' .. req.user .. ') '
else
str = req.type .. ' (' .. req.user .. ') '
end
end
end
return str
end
-- ===============================
-- if the request was approved, the associated function is called, otherwise it is deleted
-- TODO Rather than hard-coding functions below, this should be given an arbitrary result function based on the request
function modpol.orgs:resolve_request(request_id, approve)
-- wipe actions
self:wipe_pending_actions(request_id)
if approve then
local request = self.requests[request_id]
local p = request.params
-- there's probably a way to clean this up, the issue is the varying number of commands
-- ex: self['add_member'](self, 'member_name')
-- not sure if this is safe, more testing to do
-- self[request.type](self, p[1], p[2], p[3])
if request.type == "add_org" then
self:add_org(request.params[1], request.user)
elseif request.type == "delete" then
self:delete()
elseif request.type == "add_member" then
self:add_member(request.params[1])
elseif request.type == "remove_member" then
self:remove_member(request.params[1])
end
end
self.processes[request_id] = "deleted"
modpol.ocutil.log('Deleted process #' .. request_id)
self.requests[request_id] = "deleted"
modpol.ocutil.log("Resolved request #" .. request_id .. ' in ' .. self.name)
self:record("Resolved request #" .. request_id, "resolve_request")
end
-- ================================
-- tries to make a request to the org
function modpol.orgs:make_request(request)
-- makes sure the request has the valid number of parameters
local num_params = modpol.orgs.request_params[request.type]
if num_params == nil then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request type is invalid')
return false
end
-- request.params should match the num of params for that type
if #request.params ~= num_params then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has invalid number of parameters')
return false
end
-- checking to see if identical request already exists
for k, v in ipairs(self.requests) do
if self.comp_req(request, v) == true then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has already been made')
return false
end
end
-- checking to see if user is able to make request
local requested_policy = self.policies[request.type]
-- if not the instance org (instance's don't have parents)
if self.id ~= 1 then
local parent_policy = modpol.orgs.get_org(self.parent).policies[request.type]
-- tries to use org's policy table, defers to parent otherwise
if not requested_policy then
modpol.ocutil.log(request.type .. ' policy not found, deferring to parent org')
requested_policy = parent_policy
if not parent_policy then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> parent policy undefined')
return false
end
end
-- fails if instance policy undefined
else
if not requested_policy then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> policy undefined')
return false
end
end
-- make sure user is allowed to make request
if requested_policy.must_be_member and not self:has_member(request.user) then
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> user must be org member to make this request')
return false
end
local empty_index = nil
-- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.requests) do
if v == 'deleted' then
empty_index = k
break
end
end
-- attempts to fill empty spots in list, otherwise appends to end
local request_id = nil
if empty_index then
self.requests[empty_index] = request
request_id = empty_index
else
table.insert(self.requests, request)
request_id = #self.requests
end
modpol.interactions.message(request.user, "Request made by " .. request.user .. " to " .. request.type .. " in " .. self.name)
modpol.ocutil.log("Request made by " .. request.user .. " to " .. request.type .. " in " .. self.name)
self:record("Request made by " .. request.user .. " to " .. request.type, "make_request")
-- launching process tied to this request
local process_id = self:create_process(requested_policy.process_type, request_id)
-- returns process id of processes launched by this request
return process_id
end
-- wrapper for process:interact function, ensures that user actually has a pending action for that process
function modpol.orgs:interact(process_id, user)
process = self.processes[process_id]
if self.pending[user] then
if self.pending[user][process_id] == true then
process:interact(user)
else
modpol.ocutil.log("Cannot interact with process, user does not have a valid pending action")
end
else
modpol.ocutil.log("Cannot interact with process, user does not have any pending actions")
end
end

View File

@ -1,31 +0,0 @@
dofile("../modpol.lua")
print('\nRemoving existing orgs')
modpol.orgs.reset()
print('\nCreating an org called "test_org"')
test_org = modpol.instance:add_org('test_org', 'luke')
print('\nAdding user "nathan" to test_org')
test_org:add_member('nathan')
print("\nOkay, let's start with requests. Setting up an org...")
modpol.instance:add_org('test_org', 'luke')
test_org:add_member('nathan')
print('\nMaking consent the add_member policy')
test_org:set_policy("add_member", "consent", false);
print('\nMaking a request to add Josh')
add_josh = {
user = "josh",
type = "add_member",
params = {"josh"}
}
request_id = test_org:make_request(add_josh)
print('\nHave the two members vote on it')
modpol.interactions.org_dashboard("nathan","test_org")
modpol.interactions.org_dashboard("luke","test_org")

View File

@ -1,29 +0,0 @@
-- ===================================================================
-- /users.lua
-- User-related functions for Modular Politics
-- Called by modpol.lua
-- ===================================================================
-- Function: modpol.list_users
-- Params: org
-- Outputs: Table of user names
--
-- This may be overwritten by the platform-specific interface
modpol.list_users = function(org)
local users = {}
if (org == nil) then -- no specified org; all players
if modpol.instance
and modpol.instance.members then
-- if instance exists and has membership
users = modpol.instance.members
else
users = {}
end
else -- if an org is specified
if (modpol.orgs[org] ~= nil) then -- org exists
users = modpol.orgs[org]["members"]
end
end
return users
end

23
modpol_core/api.lua Normal file
View File

@ -0,0 +1,23 @@
--call all files in this directory
local localdir = modpol.topdir
--orgs
dofile (localdir .. "/orgs/base.lua")
dofile (localdir .. "/orgs/process.lua")
--interactions
dofile (localdir .. "/interactions/interactions.lua")
--modules
--TODO make this automatic and directory-based
dofile (localdir .. "/modules/add_child_org_consent.lua")
dofile (localdir .. "/modules/change_modules.lua")
dofile (localdir .. "/modules/consent.lua")
dofile (localdir .. "/modules/join_org_consent.lua")
dofile (localdir .. "/modules/leave_org.lua")
dofile (localdir .. "/modules/remove_child_consent.lua")
dofile (localdir .. "/modules/remove_member_consent.lua")
dofile (localdir .. "/modules/remove_org_consent.lua")
dofile (localdir .. "/modules/remove_org.lua")
dofile (localdir .. "/modules/rename_org_consent.lua")

View File

@ -20,7 +20,7 @@ function modpol.interactions.dashboard(user)
modpol.instance:add_member(user)
end
local all_users = modpol.list_users()
local all_users = modpol.instance:list_members()
print('All orgs: (user orgs indicated by *)')
for id, org in ipairs(modpol.orgs.array) do
@ -34,25 +34,27 @@ function modpol.interactions.dashboard(user)
print('All users: ' .. table.concat(all_users, ', '))
print()
print('Access which org?')
print('Access which org id?')
local sel = io.read()
print()
local sel_org = modpol.orgs.array[tonumber(sel)].name
if not sel_org then return end
modpol.interactions.org_dashboard(user, sel_org)
if modpol.orgs.array[tonumber(sel)] then
local sel_org = modpol.orgs.array[tonumber(sel)].name
modpol.interactions.org_dashboard(user, sel_org)
else
print("Org id not found.")
end
end
-- Function: modpol.interactions.org_dashboard
-- Params: user (string), org_name (string)
-- Params: user (string), org_string (string or id)
-- Output: Displays a menu of org-specific commands to the user
function modpol.interactions.org_dashboard(user, org_name)
local org = modpol.orgs.get_org(org_name)
function modpol.interactions.org_dashboard(user, org_string)
local org = modpol.orgs.get_org(org_string)
if not org then return nil end
-- identify parent
local parent = ""
if org.id == 1 then
parent = "none"
@ -60,66 +62,67 @@ function modpol.interactions.org_dashboard(user, org_name)
parent = modpol.orgs.get_org(org.parent).name
end
-- identify children
local children = {}
for k,v in ipairs(org.children) do
local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name)
end
local process_msg = #org.processes .. " total"
-- list available modules
local org_modules = {}
for k,v in pairs(org.modules) do
if not v.hide then
table.insert(org_modules, v.slug)
end
end
-- list pending
local process_msg = #org.processes .. " total processes"
if org.pending[user] then
process_msg = process_msg .. " (" .. #org.pending[user] .. " pending)"
else
process_msg = process_msg .. " (0 pending)"
end
-- set up output
print("Org: " .. org_name)
print("Org: " .. org.name)
print("Parent: " .. parent)
print("Members: " .. table.concat(org.members, ", "))
print("Children: " .. table.concat(children, ", "))
print("Processes: " .. process_msg)
print("Modules: " .. table.concat(org_modules, ", "))
print("Pending: " .. process_msg)
print()
print("Commands: (L)eave, (J)oin, (P)rocesses, (A)dd child, (D)elete org")
print("Commands: (M)odules, (P)ending")
local sel = io.read()
print()
if sel == 'l' or sel == 'L' then
org:remove_member(user)
elseif sel == 'j' or sel == 'J' then
org:make_request({user=user, type="add_member", params={user}})
if sel == 'm' or sel == 'M' then
print("Type module name: ")
local module_sel = io.read()
print()
local module_result = false
for k,v in ipairs(org_modules) do
if v == module_sel then
module_result = true
end
end
if module_result then
org:call_module(module_sel, user)
else
print("Error: Module not found.")
end
elseif sel == 'a' or sel == 'A' then
print("What should the new org be named?")
local new_org_name = io.read()
org:make_request({user=user, type="add_org", params={new_org_name}})
elseif sel == 'd' or sel == 'D' then
org:make_request({user=user, type="delete", params={}})
elseif sel == 'p' or sel == 'P' then
local processes = {}
print("All processes: (* indicates pending action)")
print("All processes: (* indicates pending)")
for k,v in ipairs(org.processes) do
local this_request = org.requests[v.request_id]
if type(this_request) == "table" then
local active = ''
if org.pending[user] then
if org.pending[user][v.id] then
active = '*'
end
local active = ''
if org.pending[user] then
if org.pending[user][v.id] then
active = '*'
end
local req_str = "[" .. v.id .. "] " ..
active .. this_request.type
if this_request.params[1] then
req_str = req_str .. ": " ..
table.concat(this_request.params, ", ")
end
print(req_str)
end
end
print()
@ -129,14 +132,13 @@ function modpol.interactions.org_dashboard(user, org_name)
if not process then return end
if org:has_pending_actions(user) then
if org.pending[user][process.id] then
process:interact(user)
org:interact(process.id, user)
end
end
else
print("Command not found")
modpol.interactions.org_dashboard(user, org.name)
end
end
-- Function: modpol.interactions.policy_dashboard
@ -228,8 +230,15 @@ end
-- ====================
-- Function: modpol.interactions.message_org
-- input: initiator (string), org_id (number), message (string)
-- input: initiator (string), org (number or string), message (string)
-- output: broadcasts message to all org members
function modpol.interactions.message_org(initiator, org, message)
local this_org = modpol.orgs.get_org(org)
local users = this_org:list_members()
for k,v in ipairs(users) do
modpol.interactions.message(v, message)
end
end
-- Function: modpol.interactions.binary_poll_org
-- input: initator (user string), org_id (number)

View File

@ -44,6 +44,9 @@ print (topdir)
-- OldCoder utilities
dofile (topdir .. "/util/ocutil/ocutil.lua")
-- Misc. utilities
dofile (topdir .. "/util/misc.lua")
-- ===================================================================
-- Persistent storage
-- must implement modpol.load_storage() and modpol.store_data()
@ -63,12 +66,19 @@ dofile (modpol.storage_file_path)
-- If available, load persistent storage into active tables
modpol.load_storage()
-- ===================================================================
-- ModPol core features
-- Modpol modules
modpol.modules = modpol.modules or {}
-- TKTK need to specify modules to include
-- ===================================================================
-- Modpol core features
dofile (topdir .. "/api.lua")
-- ===================================================================
-- Final checks
@ -90,7 +100,6 @@ end
-- create instance if not present
modpol.instance = modpol.orgs.array[1] or modpol.orgs.init_instance()
modpol.ocutil.log ("modpol loaded")
-- ===================================================================

View File

@ -0,0 +1,75 @@
--- @module add_child_org_consent
-- Adds a child org
-- Depends on `consent`
local add_child_org_consent = {
name = "Add child org (consent)",
slug = "add_child_org_consent",
desc = "Create a child org in the current one with member consent"
}
add_child_org_consent.data = {
child_name = "",
result = nil
}
add_child_org_consent.config = {
}
-- @function initiate
function add_child_org_consent:initiate(result)
modpol.interactions.text_query(
self.initiator,"Child org name: ",
function(input)
if input == "" then
modpol.interactions.message(
self.initiator,
"No name entered for child org")
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
self.org:delete_process(self.id)
if result then result() end
return
elseif modpol.orgs.get_org(input) then
modpol.interactions.message(
self.initiator,
"Org name already in use")
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
self.org:delete_process(self.id)
if result then result() end
return
end
self.data.child_name = input
modpol.interactions.message(
self.initiator,
"Proposed child org: " .. input)
-- initiate consent process
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Create child org " ..
self.data.child_name .. "?",
votes_required = #self.org.members
},
function()
self:create_child_org()
end
)
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
end
)
end
function add_child_org_consent:create_child_org()
self.org:add_org(self.data.child_name, self.initiator)
modpol.interactions.message_org(
self.initiator,
self.org.name,
"Child org created: "..self.data.child_name)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
end
--- (Required) Add to module table
modpol.modules.add_child_org_consent = add_child_org_consent

View File

@ -0,0 +1,171 @@
--- change_modules
-- @module change_modules
-- Depends on consent
local change_modules = {
name = "Change modules (consent)",
slug = "change_modules",
desc = "Add or remove modules from the org with member consent",
hide = false;
}
change_modules.data = {
result = nil
}
change_modules.config = {
}
function change_modules:initiate(result)
self.data.result = result
-- Step 1: add or remove?
modpol.interactions.dropdown_query(
self.initiator, "Module change options:",
{"Add module","Remove module"},
function(input)
if input == "Add module" then
self:add_module()
elseif input == "Remove module" then
self:remove_module()
end
end
)
end
function change_modules:add_module()
-- prepare module options
local available_modules = modpol.copy_table(modpol.modules)
for k,org_mod in pairs(self.org.modules) do
if available_modules[org_mod.slug] then
available_modules[org_mod.slug] = nil
end end
-- present module options
local modules_list = {}
for k,v in pairs(available_modules) do
table.insert(modules_list,v.name)
end
if #modules_list == 0 then
modpol.interactions.message(
self.initiator, "Org has all modules")
modpol.interactions.org_dashboard(
self.initiator, self.org.id)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
return
end
table.sort(modules_list)
-- now ask which to add
modpol.interactions.dropdown_query(
self.initiator, "Choose a module to add:",
modules_list,
function(mod_choice)
-- confirm choice
modpol.interactions.binary_poll_user(
self.initiator,
"Confirm: propose to add module \"" ..
mod_choice .. "\"?",
function(input)
if input == "Yes" then
self:propose_change("add",mod_choice)
modpol.interactions.org_dashboard(
self.initiator, self.org.id)
else
self:add_module()
end
end
)
end
)
end
function change_modules:remove_module()
-- prepare module options
local available_modules = {}
for k,org_mod in pairs(self.org.modules) do
if not org_mod.hide then
available_modules[org_mod.slug] = modpol.copy_table(org_mod)
end end
local modules_list = {}
local modules_count = 0
for k,v in pairs(available_modules) do
table.insert(modules_list,v.name)
modules_count = modules_count + 1
end
-- abort if no modules to remove
if modules_count == 0 then
modpol.interactions.message(
self.initiator, "Org has no modules")
modpol.interactions.org_dashboard(
self.initiator, self.org.id)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
return
end
table.sort(modules_list)
-- now ask which to remove
modpol.interactions.dropdown_query(
self.initiator, "Choose a module to remove:",
modules_list,
function(mod_choice)
-- confirm choice
modpol.interactions.binary_poll_user(
self.initiator,
"Confirm: propose to remove module \"" .. mod_choice .. "\"?",
function(input)
if input == "Yes" then
self:propose_change("remove",mod_choice)
modpol.interactions.org_dashboard(
self.initiator, self.org.id)
else
self:remove_module()
end
end
)
end
)
end
--- propose_change
-- @field type "add" or "remove"
function change_modules:propose_change(type, mod_text)
self.org:call_module(
"consent",self.initiator,
{
prompt = "Do you consent to "..type..
" this module in org "..self.org.name..
":\n"..mod_text,
votes_required = #self.org.members
},
function()
if type == "add" then
for k,v in pairs(modpol.modules) do
if v.name == mod_text then
table.insert(self.org.modules,v)
end
end
modpol.interactions.message_org(
self.initiator,self.org.id,
"Consent reached:\nAdding \""
..mod_text.."\" to org "..self.org.name)
elseif type == "remove" then
modpol.msg("Removing!")
local i = 0
for k,v in pairs(self.org.modules) do
i = i + 1
if v.name == mod_text then
modpol.msg("got it!")
self.org.modules[k] = nil
end
end
modpol.interactions.message_org(
self.initiator,self.org.id,
"Consent reached:\nRemoving \""
..mod_text.."\" from org "..self.org.name)
end
end)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
end
--- (Required) Add to module table
modpol.modules.change_modules = change_modules

View File

@ -0,0 +1,56 @@
--- @module consent
-- A utility module for checking consent
local consent = {
name = "Consent process utility",
slug = "consent",
desc = "A module other modules use for consent decisions",
hide = true
}
consent.data = {
votes = 0
}
consent.config = {
prompt = "Do you consent?",
votes_required = 1
}
function consent:initiate(result)
self.data.result = result
-- if org is empty, consent is given automatically
if self.org:get_member_count() == 0 then
if self.data.result then
self.data.result() end
self.org:wipe_pending_actions(self.id)
else
-- otherwise, create poll
for id, member in pairs(self.org.members) do
self.org:add_pending_action(self.id, member, "callback")
end
end
end
function consent:callback(member)
modpol.interactions.binary_poll_user(
member,
self.config.prompt,
function (resp)
self.org:remove_pending_action(self.id,member)
if resp == "Yes" then
self.data.votes = self.data.votes + 1
end
if self.data.votes >= self.config.votes_required then
if self.data.result then
self.data.result() end
self.org:wipe_pending_actions(self.id)
self.org:delete_process(self.id)
end
modpol.interactions.org_dashboard(
member, self.org.name)
end
)
end
modpol.modules.consent = consent

View File

@ -0,0 +1,39 @@
join_org = {}
join_org.setup = {
name = "Join Org",
slug = "join_org",
desc = "If consent process is passed, initiator joins this org."
}
function join_org.initiate(result)
modpol.interactions.binary_poll_user(
initiator,
"Would you like to join " .. org.name,
function (resp)
if resp == "Yes" then
self.org:add_member(self.initiator)
end
end
)
for i, member in ipairs(self.org.members) do
self.org:add_pending_action(
member,
function ()
modpol.interactions.binary_poll_user(
member,
"Let " .. initiator .. " join " .. org.name .. "?",
function (resp)
end
)
end
)
end
if result then result() end
end
modpol.modules.join_org = join_org

View File

@ -0,0 +1,51 @@
--- Join org (consent)
-- A simple module that calls a consent process on an org to add a member.
-- Depends on the Consent module.
local join_org_consent = {
name = "Join this org (consent)",
slug = "join_org_consent",
desc = "Adds member with consent of all members"
}
join_org_consent.data = {
result = nil
}
join_org_consent.config = {
}
function join_org_consent:initiate(result)
if self.org:has_member(self.initiator) then
modpol.interactions.message(
self.initiator,
"You are already a member of this org")
if result then result() end
self.org:delete_process(self.id)
else
self.data.result = result
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Allow " .. self.initiator .. " to join?",
votes_required = #self.org.members
},
function ()
self:complete()
end
)
end
end
function join_org_consent:complete()
self.org:add_member(self.initiator)
modpol.interactions.message_org(
self.initiator,self.org.name,
"Consent reached: " .. self.initiator ..
" joined org " .. self.org.name)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
end
modpol.modules.join_org_consent = join_org_consent

View File

@ -0,0 +1,39 @@
--- leave_org
-- @module leave_org
local leave_org = {
name = "Leave org",
slug = "leave_org",
desc = "Remove yourself from the current org"
}
leave_org.data = {
}
leave_org.config = {
}
--- (Required): initiate function
-- Modules have access to the following instance variables:
-- @param result (optional) Callback if this module is embedded in other modules
-- @function initiate
function leave_org:initiate(result)
if self.org == modpol.instance then
modpol.interactions.message(
self.initiator,
"You cannot leave the root org")
else
self.org:remove_member(self.initiator)
modpol.interactions.message_org(
self.initiator,self.org.id,
self.initiator .. " has left org " .. self.org.name)
modpol.interactions.message(
self.initiator,
"You have left org " .. self.org.name)
end
if result then result() end
self.org:delete_process(self.id)
end
--- (Required) Add to module table
modpol.modules.leave_org = leave_org

View File

@ -0,0 +1,71 @@
--- Remove child (consent)
-- A simple module that calls a consent process on an org to remove its child
-- Depends on the Consent module.
local remove_child_consent = {
name = "Remove child (consent)",
slug = "remove_child_consent",
desc = "Removes a child org if all members of this org consent."
}
remove_child_consent.data = {
result = nil,
child_to_remove = nil
}
remove_child_consent.config = {
}
function remove_child_consent:initiate(result)
local children = {}
for i,v in ipairs(self.org.children) do
local child = modpol.orgs.get_org(v)
if child then table.insert(children, child.name) end
end
-- Abort if no child orgs
if #children == 0 then
modpol.interactions.message(
self.initiator,
"Org has no children")
if result then result() end
self.org:delete_process(self.id)
else
self.data.result = result
modpol.interactions.dropdown_query(
self.initiator,
"Choose a child of org "..
self.org.name.." to remove:",
children,
function(input)
self.data.child_to_remove = modpol.orgs.get_org(input)
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Remove child org "..input.."?",
votes_required = #self.org.members
},
function()
self:complete()
end)
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
end)
end
end
function remove_child_consent:complete()
modpol.interactions.message_org(
self.initiator, self.data.child_to_remove.id,
"Removing org " .. self.data.child_to_remove.name ..
" by parent org consent")
modpol.interactions.message_org(
self.initiator, self.org.id,
"Consent reached: removing org " ..
self.data.child_to_remove.name)
self.data.child_to_remove:delete()
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
end
modpol.modules.remove_child_consent = remove_child_consent

View File

@ -0,0 +1,64 @@
--- remove_member_consent
-- @module remove_member_consent
local remove_member_consent = {
name = "Remove a member (consent)",
slug = "remove_member_consent",
desc = "Removes org member with consent of other members"
}
remove_member_consent.data = {
member_to_remove = "",
result = nil
}
remove_member_consent.config = {
}
function remove_member_consent:initiate(result)
-- Abort if in root org
if self.org == modpol.instance then
modpol.interactions.message(
self.initiator,
"Members cannot be removed from the root org")
if result then result() end
self.org:delete_process(self.id)
else -- proceed if not root
self.data.result = result
modpol.interactions.dropdown_query(
self.initiator,
"Which member of org "..self.org.name..
" do you want to remove?",
self.org.members,
function(input)
self.data.member_to_remove = input
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Remove "..input..
" from org "..self.org.name.."?",
votes_required = #self.org.members - 1
},
function()
self:complete()
end)
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
end)
end
end
function remove_member_consent:complete()
modpol.interactions.message_org(
self.initiator, self.org.id,
"Consent reached: removing "..
self.data.member_to_remove..
" from org "..self.org.name)
self.org:remove_member(self.data.member_to_remove)
self.org:delete_process(self.id)
if self.data.result then self.data.result() end
end
--- (Required) Add to module table
modpol.modules.remove_member_consent = remove_member_consent

View File

@ -0,0 +1,33 @@
--- @module Remove Org
-- A simple module that removes an org.
--- Main module table
remove_org = {
name = "Remove this org",
slug = "remove_org",
desc = "Eliminates the org and all child orgs."
}
remove_org.config = {}
remove_org.data = {}
--- Initiate function
-- @function initiate
function remove_org:initiate(result)
if self.org == modpol.instance then
modpol.interactions.message(
self.initiator,
"Cannot remove the root org")
else
modpol.interactions.message_org(
self.initiator,self.org.id,
"Removing org: "..self.org.name)
self.org:delete()
modpol.interactions.dashboard(self.initiator)
-- call result function
end
if result then result() end
end
modpol.modules.remove_org = remove_org

View File

@ -0,0 +1,52 @@
--- Remove org (consent)
-- A simple module that calls a consent process on an org to remove it.
-- Depends on the Consent module.
local remove_org_consent = {
name = "Remove this org (consent)",
slug = "remove_org_consent",
desc = "Removes an org if all members consent."
}
remove_org_consent.data = {
result = nil
}
remove_org_consent.config = {
}
function remove_org_consent:initiate(result)
if self.org == modpol.instance then
modpol.interactions.message(
self.initiator,
"Cannot remove root org")
if result then result() end
self.org:delete_process(self.id)
else
self.data.result = result
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Remove org " .. self.org.name .. "?",
votes_required = #self.org.members
},
function ()
self:complete()
end
)
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
end
end
function remove_org_consent:complete()
modpol.interactions.message_org(
self.initiator, self.org.id,
"Consent reached: removing org " .. self.org.name)
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
self.org:delete()
end
modpol.modules.remove_org_consent = remove_org_consent

View File

@ -0,0 +1,77 @@
--- Rename org (consent)
-- A simple module that calls a consent process on an org to rename it.
-- Depends on the Consent module.
local rename_org_consent = {
name = "Rename this org (consent)",
slug = "rename_org_consent",
desc = "Renames an org if all members consent."
}
rename_org_consent.data = {
result = nil,
new_name = nil
}
rename_org_consent.config = {
}
function rename_org_consent:initiate(result)
modpol.interactions.text_query(
self.initiator,"New org name: ",
function(input)
if input == "" then
modpol.interactions.message(
self.initiator,
"No name entered for child org")
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
self.org:delete_process(self.id)
if result then result() end
return
elseif modpol.orgs.get_org(input) then
modpol.interactions.message(
self.initiator,
"Org name already in use")
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
self.org:delete_process(self.id)
if result then result() end
return
end
self.data.new_name = input
modpol.interactions.message(
self.initiator,
"Proposed to change name of org " ..
self.org.name .. " to " .. input)
-- initiate consent process
self.org:call_module(
"consent",
self.initiator,
{
prompt = "Change name of org " ..
self.org.name .. " to " .. input .. "?",
votes_required = #self.org.members
},
function()
self:complete()
end
)
modpol.interactions.org_dashboard(
self.initiator, self.org.name)
end
)
end
function rename_org_consent:complete()
modpol.interactions.message_org(
self.initiator,
self.org.name,
"Changing name of org " .. self.org.name ..
" to " .. self.data.new_name)
self.org.name = self.data.new_name
if self.data.result then self.data.result() end
self.org:delete_process(self.id)
end
modpol.modules.rename_org_consent = rename_org_consent

View File

@ -0,0 +1,54 @@
--- module_template
-- @module module_template
--- (Required): data table containing name and description of the module
-- @field name "Human-readable name (parens OK, no brackets)"
-- @field slug "Same as module class name"
-- @field desc "Description of the module"
-- @field hide "Whether this is a hidden utility module"
local module_template = {
name = "Module Human-Readable Name",
slug = "template",
desc = "Description of the module",
hide = false;
}
--- (Required) Data for module
-- Variables that module uses during the course of a process
-- Can be blank
module_template.data = {
}
--- (Required): config for module
-- Defines the input parameters to the module initiate function.
-- Can be blank
-- When calling a module from within another module,
-- variables not defined in config will be ignored.
-- Default values set in config can be overridden
-- @field field_1 ex: votes_required, default = 5
-- @field field_2 ex: voting_type, default = "majority"
module_template.config = {
field_1 = 5
field_2 = "majority"
}
--- (Required): initiate function
-- Modules have access to the following instance variables:
-- <li><code>self.org</code> (the org the module was called in),</li>
-- <li><code>self.initiator</code> (the user that callced the module),</li>
-- <li><code>self.id</code> (the process id of the module instance)</li>
-- @param result (optional) Callback if this module is embedded in other modules
-- @function initiate
function module_template:initiate(result)
-- call interaction functions here!
-- concluding functions:
-- call these wherever process might end;
-- may need to put result in self.data.result
-- if process ends in another function
if result then result() end
self.org:delete_process(self.id)
end
--- (Required) Add to module table
modpol.modules.module_template = module_template

View File

@ -1,3 +1,6 @@
--- Orgs: Base
-- Basic functions for orgs
modpol.orgs = modpol.orgs or
{
count = 1,
@ -11,14 +14,8 @@ function temp_org()
return {
id = nil,
name = nil,
policies = {
add_org={process_type='consent', must_be_member=false},
delete={process_type='consent', must_be_member=false},
add_member={process_type='consent', must_be_member=false},
remove_member={process_type='consent', must_be_member=false}
},
modules = modpol.copy_table(modpol.modules),
processes = {},
requests = {},
pending = {},
members = {},
parent = nil,
@ -177,7 +174,8 @@ function modpol.orgs:add_org(name, user)
child_org.id = modpol.orgs.count
child_org.name = name
child_org.parent = self.id
child_org.processes = self.processes
child_org.processes = {}
child_org.modules = self.modules
setmetatable(child_org, modpol.orgs)

View File

@ -0,0 +1,101 @@
--- Process functions for orgs
function modpol.orgs:call_module(module_slug, initiator, config, result)
if not modpol.modules[module_slug] then
modpol.ocutil.log('Error in ' .. self.name .. ':call_module -> module "' .. module_slug .. '" not found')
return
end
local empty_index = nil
-- linear search for empty process slots (lazy deletion)
for k, v in ipairs(self.processes) do
if v == 'deleted' then
empty_index = k
break
end
end
local index
-- attempts to fill empty spots in list, otherwise appends to end
if empty_index then
index = empty_index
else
index = #self.processes + 1
end
local module = modpol.modules[module_slug]
-- sets default values for undeclared config variables
if #module.config > 0 then
for k, v in pairs(module.config) do
if config[k] == nil then
config[k] = v
end
end
end
-- setting default params
local new_process = {
metatable = {__index = module},
initiator = initiator,
org = self,
id = index,
config = config,
data = module.data,
slug = module_slug
}
setmetatable(new_process, new_process.metatable)
self.processes[index] = new_process
self.processes[index]:initiate(result)
return index
end
function modpol.orgs:delete_process(id)
self.processes[id] = 'deleted'
end
function modpol.orgs:add_pending_action(process_id, user, callback)
self.pending[user] = self.pending[user] or {}
self.pending[user][process_id] = callback
modpol.interactions.message(
user, "New pending action in org "..self.name)
end
function modpol.orgs:remove_pending_action(process_id, user)
if self.pending[user] then
self.pending[user][process_id] = nil
end
end
function modpol.orgs:wipe_pending_actions(process_id)
for user in pairs(self.pending) do
self.pending[user][process_id] = nil
end
end
function modpol.orgs:has_pending_actions(user)
-- next() will return the next pair in a table
-- if next() returns nil, the table is empty
if not self.pending[user] then
return false
else
if not next(self.pending[user]) then
return false
else
return true
end
end
end
function modpol.orgs:interact(process_id, user)
local process = self.processes[process_id]
if self.pending[user] then
local callback = self.pending[user][process_id]
if callback then
process[callback](process, user)
end
end
end

View File

@ -0,0 +1,13 @@
dofile('../modpol.lua');
modpol.orgs.reset()
test_org = modpol.instance:add_org('test_org', 'luke')
test_org:add_member('nathan')
print(table.concat(test_org:list_members(), ", "))
test_org:call_module(
"join_org_consent",
"paul"
)

View File

@ -0,0 +1,15 @@
dofile('../modpol.lua');
modpol.orgs.reset()
test_org = modpol.instance:add_org('test_org', 'luke')
test_org:add_member('nathan')
print(table.concat(test_org:list_members(), ", "))
-- modpol.modules.join_org.initiate("paul", test_org)
test_org:call_module(
"join_org_class",
"paul"
)

View File

@ -0,0 +1,9 @@
--- @function modpol.copy_table
-- Returns a copy of the table inputted
function modpol.copy_table(t)
local t2 = {}
for k,v in pairs(t) do
t2[k] = v
end
return t2
end

View File

@ -97,7 +97,7 @@ modpol.ocutil.log = function (s)
if modpol.ocutil.logdir ~= nil and
modpol.ocutil.logname ~= nil then
s = s .. "\n" -- Add trailing newline
s = s .. "\n" -- Add trailing newline
local logpath = modpol.ocutil.logdir .. "/" .. modpol.ocutil.logname
modpol.ocutil.file_append (logpath, s)

View File

@ -1,6 +1,4 @@
--call all files in this directory
--- Script for loading Minetest files
-- ===================================================================
-- Minetest Specific Overrides of modpol functions
@ -9,32 +7,32 @@
local localdir = minetest.get_modpath("modpol") .. "/modpol_minetest"
--Users
dofile (localdir .. "/overrides/users/users.lua")
--orgs
--dofile (localdir .. "/overrides/orgs/orgs.lua")
--interactions
dofile (localdir .. "/overrides/interactions/interactions.lua")
-- messaging functions
-- dofile (localdir .. "/overrides/processes/processes.lua")
--overrides
dofile (localdir .. "/overrides/interactions.lua")
--testing command for "singleplayer"
function modpol.msg(text)
modpol.interactions.message("singleplayer",text)
end
-- ===================================================================
-- Minetest Chatcommands
-- ===================================================================
dofile (localdir .. "/chatcommands/chatcommands.lua")
dofile (localdir .. "/chatcommands.lua")
-- ===================================================================
-- Minetest Specific code
-- Minetest-specific modules
-- ===================================================================
dofile (localdir .. "/modules/priv_to_org.lua")
-- orgs
-- ===================
-- ===================================================================
-- Minetest-specific code
-- ===================================================================
dofile (localdir .. "/orgs/instance.lua") --add players to the instance when they join.
--add members to the instance, if they are not already there.
minetest.register_on_joinplayer(function(player)
local p_name = player:get_player_name()
modpol.instance:add_member(p_name)
end)

View File

@ -15,10 +15,10 @@ regchat = function(name, command_table)
end
-- ===================================================================
-- /modpol
-- /mp
-- Presents a menu of options to users
regchat(
"modpol", {
"mp", {
privs = {},
func = function(user)
modpol.interactions.dashboard(user)
@ -26,14 +26,17 @@ regchat(
})
-- ===================================================================
-- /reset
-- For testing only
-- /mptest
-- For testing only, accessible to admin users
-- Clears the system and recreates instance with all players
-- opens dashboard too for fun.
regchat(
"reset", {
privs = {},
"mptest", {
privs = {privs=true},
func = function(user)
modpol.orgs.reset();
modpol.orgs.reset()
modpol.instance:add_member(user)
modpol.interactions.dashboard(user)
return true, "Reset orgs"
end,
})
@ -71,38 +74,3 @@ regchat(
end
})
-- ===================================================================
-- /listplayers
regchat(
"listplayers", {
privs = {},
func = function(user)
local result = table.concat(modpol.list_users(),", ")
return true, "All players: " .. result
end,
})
-- ===================================================================
-- /joinorg
regchat(
"joinorg", {
privs = {},
func = function(user, param)
local org = modpol.orgs.get_org(param)
local success, result = org:add_member(user)
return true, result
end,
})
-- ===================================================================
-- /pollself [question]
-- asks the user a question specified in param
regchat(
"pollself", {
privs = {},
func = function(user, param)
modpol.interactions.binary_poll_user(user, param)
return true, result
end,
})

View File

@ -0,0 +1,48 @@
--- Set privilege to org members
-- @module priv_to_org
-- Allows initiator to grant a priv they have to all members of an org
local priv_to_org = {
name = "Set privilege to org members",
slug = "priv_to_org",
desc = "Allows initiator to grant a priv they have to all members of an org"
}
priv_to_org.data = {
}
priv_to_org.config = {
}
--- (Required): initiate function
-- @param result (optional) Callback if this module is embedded in other modules
-- @function initiate
function priv_to_org:initiate(result)
local player_privs = minetest.get_player_privs(self.initiator)
-- construct table for display
local player_privs_table = {"View..."}
for k,v in pairs(player_privs) do
if player_privs[k] then
table.insert(player_privs_table,k)
end
end
modpol.interactions.dropdown_query(
self.initiator,
"Which privilege do you want to share with members of "..self.org.name.."?",
player_privs_table,
function(input)
for i,member in ipairs(self.org.members) do
local member_privs = minetest.get_player_privs(member)
member_privs[input] = true
minetest.set_player_privs(member, member_privs)
end
local message = self.initiator .. " added " .. input ..
" privilege to all members of " .. self.org.name
modpol.interactions.message_org(self.initiator, self.org.id, message)
end)
-- call result function
if result then result() end
end
--- (Required) Add to module table
modpol.modules.priv_to_org = priv_to_org

View File

@ -1,6 +0,0 @@
--add members to the instance, if they are not already there.
minetest.register_on_joinplayer(function(player)
local p_name = player:get_player_name()
modpol.instance:add_member(p_name)
end)

View File

@ -47,23 +47,19 @@ function modpol.interactions.dashboard(user)
-- to add: nested orgs map
local all_orgs = modpol.orgs.list_all()
local user_orgs = modpol.orgs.user_orgs(user)
local all_users = modpol.list_users()
local all_users = modpol.instance:list_members()
-- set up formspec
local formspec = {
"formspec_version[4]",
"size[10,8]",
"label[0.5,0.5;MODPOL DASHBOARD]",
"size[10,6]",
"label[0.5,0.5;M O D U L A R P O L I T I C S]",
"label[0.5,2;All orgs:]",
"dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
"dropdown[2,1.5;7,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
"label[0.5,3;Your orgs:]",
"dropdown[2,2.5;5,0.8;user_orgs;"..formspec_list(user_orgs)..";;]",
"dropdown[2,2.5;7,0.8;user_orgs;"..formspec_list(user_orgs)..";;]",
"label[0.5,4;All users:]",
"dropdown[2,3.5;5,0.8;all_users;"..formspec_list(all_users)..";;]",
"button[0.5,7;1,0.8;test_poll;Test poll]",
"button[2,7;1,0.8;add_org;Add org]",
"button[3.5,7;1.5,0.8;remove_org;Remove org]",
"button[5.5,7;1.5,0.8;reset_orgs;Reset orgs]",
"button_exit[8.5,7;1,0.8;close;Close]",
"dropdown[2,3.5;7,0.8;all_users;"..formspec_list(all_users)..";;]",
"button_exit[8.5,5;1,0.8;close;Close]",
}
local formspec_string = table.concat(formspec, "")
-- present to player
@ -75,26 +71,7 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
local pname = player:get_player_name()
if nil then
-- buttons first
elseif fields.test_poll then
-- FOR TESTING PURPOSES ONLY
modpol.interactions.text_query(
pname,"Poll question:",
function(input)
modpol.interactions.binary_poll_user(
pname, input,
function(vote)
modpol.interactions.message(
pname, pname .. " voted " .. vote)
end)
end)
elseif fields.add_org then
modpol.interactions.add_org(pname, 1)
elseif fields.remove_org then
modpol.interactions.remove_org(pname)
elseif fields.reset_orgs then
modpol.orgs.reset()
modpol.instance:add_member(pname)
modpol.interactions.dashboard(pname)
-- none here right now!
-- Put all dropdowns at the end
elseif fields.close then
@ -108,86 +85,84 @@ end)
-- Function: modpol.interactions.org_dashboard
-- Params: user (string), org_name (string)
-- Params: user (string), org_string (string or num)
-- Output: Displays a menu of org-specific commands to the user
function modpol.interactions.org_dashboard(user, org_name)
function modpol.interactions.org_dashboard(user, org_string)
-- prepare data
local org = modpol.orgs.get_org(org_name)
local org = modpol.orgs.get_org(org_string)
if not org then return nil end
local is_member = org:has_member(user)
local membership_toggle = function()
local toggle_code = ""
if is_member then
toggle_code = toggle_code
..minetest.formspec_escape("leave")..";"
..minetest.formspec_escape("Leave").."]"
else
toggle_code = toggle_code
..minetest.formspec_escape("join")..";"
..minetest.formspec_escape("Join").."]"
local function membership_toggle(org_display)
local current_org = modpol.orgs.get_org(org_display)
if current_org then
if current_org:has_member(user) then
return " (member)"
end
end
return toggle_code
return ""
end
-- identify parent
local parent = modpol.orgs.get_org(org.parent)
if parent then parent = parent.name
else parent = "none" end
-- prepare children menu
local children = {"View..."}
local children = {}
for k,v in ipairs(org.children) do
local this_child = modpol.orgs.get_org(v)
table.insert(children, this_child.name)
end
-- prepare policies menu
local policies = {"View..."}
for k,v in pairs(org.policies) do
table.insert(policies, k .. ": " ..
org.policies[k].process_type)
end
table.insert(policies, "Add policy")
-- prepare processes menu
local processes = {"View..."}
for k,v in ipairs(org.processes) do
local this_request = org.requests[v.request_id]
if type(this_request) == "table" then
local active = ''
if org.pending[user] then
if org.pending[user][v.id] then
active = '*'
end
table.sort(children)
table.insert(children,1,"View...")
-- prepare modules menu
local modules = {}
if org.modules then
for k,v in pairs(org.modules) do
if not v.hide then -- hide utility modules
local module_entry = v.name..
" ["..v.slug.."]"
table.insert(modules, module_entry)
end
local req_str = "[" .. v.id .. "] " ..
active .. this_request.type
if this_request.params[1] then
req_str = req_str .. ": " ..
table.concat(this_request.params, ", ")
end
table.insert(processes, req_str)
end
end
table.sort(modules)
table.insert(modules,1,"View...")
-- prepare pending menu
local pending = {}
local num_pending = 0
if org.pending[user] then
for k,v in pairs(org.pending[user]) do
local pending_string = org.processes[k].name
.." ["..k.."]"
table.insert(pending, pending_string)
num_pending = num_pending + 1
end
end
table.sort(pending)
table.insert(pending,1,"View...")
-- set player context
local user_context = {}
user_context["current_org"] = org_name
user_context["current_org"] = org.name
_contexts[user] = user_context
-- set up formspec
local formspec = {
"formspec_version[4]",
"size[10,8]",
"label[0.5,0.5;Org: "..
minetest.formspec_escape(org_name).."]",
"label[0.5,1;Parent: "..parent.."]",
"button[8.5,0.5;1,0.8;"..membership_toggle(),
minetest.formspec_escape(org.name)..membership_toggle(org.name).."]",
"label[0.5,1;Parent: "..parent..membership_toggle(parent).."]",
"label[0.5,2;Members:]",
"dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]",
"dropdown[2,1.5;7,0.8;user_orgs;"..formspec_list(org.members)..";;]",
"label[0.5,3;Children:]",
"dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]",
"label[0.5,4;Policies:]",
"dropdown[2,3.5;5,0.8;policies;"..formspec_list(policies)..";;]",
"label[0.5,5;Processes:]",
"dropdown[2,4.5;5,0.8;processes;"..formspec_list(processes)..";;]",
"button[0.5,7;1,0.8;test_poll;Test poll]",
"button[2,7;1,0.8;add_child;Add child]",
"button[3.5,7;1.5,0.8;remove_org;Remove org]",
"dropdown[2,2.5;7,0.8;children;"..formspec_list(children)..";;]",
"label[0.5,4;Modules:]",
"dropdown[2,3.5;7,0.8;modules;"..formspec_list(modules)..";;]",
"label[0.5,5;Pending ("..num_pending.."):]",
"dropdown[2,4.5;7,0.8;pending;"..formspec_list(pending)..";;]",
"button[8.5,7;1,0.8;back;Back]",
}
local formspec_string = table.concat(formspec, "")
@ -199,72 +174,44 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:org_dashboard" then
local pname = player:get_player_name()
local org = modpol.orgs.get_org(_contexts[pname].current_org)
if nil then
elseif fields.join then
local new_request = {
user = pname,
type = "add_member",
params = {pname}
}
org:make_request(new_request)
modpol.interactions.org_dashboard(pname,org.name)
elseif fields.leave then
org:remove_member(pname)
-- just confirm the org still exists:
if not org then
modpol.interactions.message(pname, "Org no longer exists")
modpol.interactions.dashboard(pname)
elseif fields.test_poll then
modpol.interactions.binary_poll_org(
pname, org.id,
function(input)
modpol.interactions.message_org(
pname,
org.id,
"New response: " .. input)
end)
elseif fields.add_child then
modpol.interactions.text_query(
pname, "Child org name:",
function(input)
local new_request = {
user = pname,
type = "add_org",
params = {input}
}
org:make_request(new_request)
modpol.interactions.message(pname,"requested")
modpol.interactions.org_dashboard(
pname,org.name)
end)
elseif fields.remove_org then
local new_request = {
user = pname,
type = "delete",
params = {}
}
org:make_request(new_request)
modpol.interactions.org_dashboard(pname,org.name)
return end
-- okay, onward
if nil then
elseif fields.back then
modpol.interactions.dashboard(pname)
-- Put all dropdowns at the end
elseif fields.policies
and fields.policies ~= "View..." then
local policy
if fields.policies == "Add policy" then
policy = nil
elseif fields.policies == "View..." then
return
else
policy = string.match(fields.policies,"(%w+)%:")
end
modpol.interactions.policy_dashboard(
pname, org.id, policy)
elseif fields.processes
and fields.processes ~= "View..." then
local sel = string.match(fields.processes,"%[(%d)%]")
local process = org.processes[tonumber(sel)]
-- Receiving modules
elseif fields.modules
and fields.modules ~= "View..." then
local module = string.match(
fields.modules,"%[(.*)%]")
modpol.interactions.binary_poll_user(
pname,
modpol.modules[module].name..":\n"..
modpol.modules[module].desc.."\n"..
"Proceed?",
function(input)
if input == "Yes" then
org:call_module(module, pname)
end
end)
-- Receiving pending
elseif fields.pending
and fields.pending ~= "View..." then
local pending = string.match(
fields.pending,"%[(%d)%]")
local process = org.processes[tonumber(pending)]
if process then
process:interact(pname)
org:interact(process.id, pname)
end
-- Children
elseif fields.children
and fields.children ~= "View..." then
local org_name = fields.children
@ -286,20 +233,21 @@ function modpol.interactions.policy_dashboard(
end
-- BASIC INTERACTION FUNCTIONS
-- ===========================
-- INTERACTION FUNCTIONS
-- =====================
-- Function: modpol.interactions.message
-- input: message (string)
-- output
-- input: user (string), message (string)
-- output: displays message to specified user
function modpol.interactions.message(user, message)
minetest.chat_send_player(user, message)
if message then
minetest.chat_send_player(user, message)
end
end
-- Function: modpol.interactions.text_query
-- Overrides function at modpol/interactions.lua
-- input: user (string), query (string), func (function)
-- func input: user input (string)
-- output: Applies "func" to user input
function modpol.interactions.text_query(user, query, func)
-- set up formspec
@ -311,7 +259,7 @@ function modpol.interactions.text_query(user, query, func)
"button[0.5,2.5;1,0.8;yes;OK]",
}
local formspec_string = table.concat(formspec, "")
-- present to players
-- present to player
minetest.show_formspec(user, "modpol:text_query", formspec_string)
-- put func in _contexts
if _contexts[user] == nil then _contexts[user] = {} end
@ -341,6 +289,8 @@ end)
-- func input: choice (string)
-- output: calls func on user choice
function modpol.interactions.dropdown_query(user, label, options, func)
-- Add "View..." to the top of the list
table.insert(options,1,"View...")
-- set up formspec
local formspec = {
"formspec_version[4]",
@ -360,25 +310,28 @@ end
minetest.register_on_player_receive_fields(function (player, formname, fields)
if formname == "modpol:dropdown_query" then
local pname = player:get_player_name()
if fields.cancel ~= "cancel" then
if fields.cancel then
minetest.close_formspec(pname, formname)
elseif fields.input == "View..." then
-- "View...", do nothing
else
local choice = fields.input
local func = _contexts[pname]["dropdown_query_func"]
if not choice then
-- no choice, do nothing
-- empty, do nothing
elseif func then
--not sure if we should close
--breaks sequential dropdown_queries:
--minetest.close_formspec(pname, formname)
func(choice)
else
minetest.close_formspec(pname, formname)
modpol.interactions.message(pname, "dropdown_query: " .. choice)
end
end
minetest.close_formspec(pname, formname)
end
end)
-- SECONDARY INTERACTIONS
-- ======================
-- Function: modpol.binary_poll_user(user, question, function)
-- Overrides function at modpol/interactions.lua
-- Params: user (string), question (string), func (function)
@ -388,12 +341,12 @@ function modpol.interactions.binary_poll_user(user, question, func)
-- set up formspec
local formspec = {
"formspec_version[4]",
"size[5,3]",
"size[8,4]",
"label[0.375,0.5;",minetest.formspec_escape(question), "]",
"button[1,1.5;1,0.8;yes;Yes]",
"button[2,1.5;1,0.8;no;No]",
--TKTK can we enable text wrapping?
--TKTK we could use scroll boxes to contain the text
"button[1,2.5;1,0.8;yes;Yes]",
"button[2,2.5;1,0.8;no;No]",
--TODO can we enable text wrapping?
--TODO we could use scroll boxes to contain the text
}
local formspec_string = table.concat(formspec, "")
if _contexts[user] == nil then _contexts[user] = {} end
@ -410,78 +363,9 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
elseif fields.no then vote = fields.no
end
if vote then
modpol.interactions.message(pname, "Responded " .. vote)
local func = _contexts[pname]["binary_poll_func"]
if func then func(vote) end
end
minetest.close_formspec(pname, formname)
end
end)
-- COMPLEX INTERACTIONS
-- ====================
-- Function: modpol.interactions.message_org
-- input: initiator (string), org_id (number), message (string)
-- output: broadcasts message to all org members
function modpol.interactions.message_org(initiator, org_id, message)
local org = modpol.orgs.get_org(org_id)
local users = org:list_members()
for k,v in ipairs(users) do
modpol.interactions.message(v, message)
end
end
-- Function: modpol.interactions.binary_poll_org
-- input: initator (user string), org_id (number)
-- output: gets question from initiator, asks all org members, broadcasts answers
-- TODO for testing. This should be implemented as a request.
function modpol.interactions.binary_poll_org(initiator, org_id, func)
local org = modpol.orgs.get_org(org_id)
local users = org:list_members()
modpol.interactions.text_query(
initiator, "Yes/no poll question:",
function(input)
for k,v in ipairs(users) do
modpol.interactions.binary_poll_user(v, input, func)
end
end)
end
-- Function: modpol.interactions.add_org
-- input: initator (user string), base_org_id (ID)
-- output: interaction begins
-- GODMODE
function modpol.interactions.add_org(user, base_org_id)
modpol.interactions.text_query(
user,"Org name:",
function(input)
local base_org = modpol.orgs.get_org(1)
local result = base_org:add_org(input, user)
local message = input .. " created"
modpol.interactions.message(user, message)
modpol.interactions.dashboard(user)
end)
end
-- Function: modpol.interactions.remove_org
-- input: initator (user string)
-- output: interaction begins
-- GODMODE
function modpol.interactions.remove_org(user)
-- start formspec
local orgs_list = modpol.orgs.list_all()
local label = "Choose an org to remove:"
modpol.interactions.dropdown_query(
user, label, orgs_list,
function(input)
if input then
local target_org = modpol.orgs.get_org(input)
local result = target_org:delete()
local message = input .. " deleted"
modpol.interactions.message(user, message)
end
modpol.interactions.dashboard(user)
end)
end

View File

@ -1,21 +0,0 @@
-- ===================================================================
-- Function: modpol.list_users(org)
-- Overwrites function at /users.lua
-- Params:
-- if nil, lists instance members; if an org name, lists its members
-- Output: a table with names of players currently in the game
modpol.list_users = function(org)
local users = {}
if (org == nil) then -- no specified org; all players
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
table.insert(users,name)
end
else -- if an org is specified
if (modpol.orgs[org] ~= nil) then -- org exists
users = modpol.orgs[org]["members"]
end
end
return users
end