1# Copyright (C) 2007-2020 by the Free Software Foundation, Inc. 2# 3# This file is part of GNU Mailman. 4# 5# GNU Mailman is free software: you can redistribute it and/or modify it under 6# the terms of the GNU General Public License as published by the Free 7# Software Foundation, either version 3 of the License, or (at your option) 8# any later version. 9# 10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT 11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13# more details. 14# 15# You should have received a copy of the GNU General Public License along with 16# GNU Mailman. If not, see <https://www.gnu.org/licenses/>. 17 18"""Interface describing the basics of a member.""" 19 20from enum import Enum 21from mailman.interfaces.errors import MailmanError 22from public import public 23from zope.interface import Attribute, Interface 24 25 26@public 27class DeliveryMode(Enum): 28 # Regular (i.e. non-digest) delivery 29 regular = 1 30 # Plain text digest delivery 31 plaintext_digests = 2 32 # MIME digest delivery 33 mime_digests = 3 34 # Summary digests 35 summary_digests = 4 36 37 38@public 39class DeliveryStatus(Enum): 40 # Delivery is enabled 41 enabled = 1 42 # Delivery was disabled by the user 43 by_user = 2 44 # Delivery was disabled due to bouncing addresses 45 by_bounces = 3 46 # Delivery was disabled by an administrator or moderator 47 by_moderator = 4 48 # Disabled for unknown reasons. 49 unknown = 5 50 51 52@public 53class MemberRole(Enum): 54 member = 1 55 owner = 2 56 moderator = 3 57 nonmember = 4 58 59 60@public 61class MembershipChangeEvent: 62 """Base class for subscription/unsubscription events.""" 63 64 def __init__(self, mlist, member): 65 self.mlist = mlist 66 self.member = member 67 68 69@public 70class SubscriptionEvent(MembershipChangeEvent): 71 """Event which gets triggered when a user joins a mailing list.""" 72 73 def __init__(self, *args, **kw): 74 send_welcome_message = kw.pop('send_welcome_message', None) 75 super().__init__(*args, **kw) 76 self.send_welcome_message = send_welcome_message 77 78 def __str__(self): 79 return '{0} joined {1}'.format(self.member.address, self.mlist.list_id) 80 81 82@public 83class UnsubscriptionEvent(MembershipChangeEvent): 84 """Event which gets triggered when a user leaves a mailing list. 85 86 One thing to keep in mind: because the IMember is deleted when the 87 unsubscription happens, this event actually gets triggered just before the 88 member is unsubscribed. 89 """ 90 91 def __str__(self): 92 return '{0} left {1}'.format(self.member.address, self.mlist.list_id) 93 94 95@public 96class MembershipError(MailmanError): 97 """Base exception for all membership errors.""" 98 99 100@public 101class AlreadySubscribedError(MembershipError): 102 """The member is already subscribed to the mailing list with this role.""" 103 104 def __init__(self, fqdn_listname, email, role): 105 super().__init__() 106 self.fqdn_listname = fqdn_listname 107 self.email = email 108 self.role = role 109 110 def __str__(self): 111 return '{0} is already a {1} of mailing list {2}'.format( 112 self.email, self.role, self.fqdn_listname) 113 114 115@public 116class MembershipIsBannedError(MembershipError): 117 """The address is not allowed to subscribe to the mailing list.""" 118 119 def __init__(self, mlist, address): 120 super().__init__() 121 self._mlist = mlist 122 self._address = address 123 124 def __str__(self): 125 return '{0} is not allowed to subscribe to {1.fqdn_listname}'.format( 126 self._address, self._mlist) 127 128 129@public 130class MissingPreferredAddressError(MembershipError): 131 """A user without a preferred address attempted to subscribe.""" 132 133 def __init__(self, user): 134 super().__init__() 135 self._user = user 136 137 def __str__(self): 138 return 'User must have a preferred address: {0}'.format(self._user) 139 140 141@public 142class NotAMemberError(MembershipError): 143 """The address is not a member of the mailing list.""" 144 145 def __init__(self, mlist, address): 146 super().__init__() 147 self._mlist = mlist 148 self._address = address 149 150 def __str__(self): 151 return '{0} is not a member of {1.fqdn_listname}'.format( 152 self._address, self._mlist) 153 154 155@public 156class IMember(Interface): 157 """A member of a mailing list.""" 158 159 member_id = Attribute( 160 """The member's unique, random identifier as a UUID.""") 161 162 list_id = Attribute( 163 """The list id of the mailing list the member is subscribed to.""") 164 165 mailing_list = Attribute( 166 """The `IMailingList` that the member is subscribed to.""") 167 168 address = Attribute( 169 """The email address that's subscribed to the list.""") 170 171 user = Attribute( 172 """The user associated with this member.""") 173 174 subscriber = Attribute( 175 """The object representing how this member is subscribed. 176 177 This will be an ``IAddress`` if the user is subscribed via an explicit 178 address, otherwise if the the user is subscribed via their preferred 179 address, it will be an ``IUser``. 180 """) 181 182 display_name = Attribute( 183 """The best match of the member's display name. 184 185 This will be `subscriber.display_name` if available, which means it 186 will either be the display name of the address or user that's 187 subscribed. If unavailable, and the address is the subscriber, then 188 the linked user's display name is given, if available. When all else 189 fails, the empty string is returned. 190 """) 191 192 preferences = Attribute( 193 """This member's preferences.""") 194 195 role = Attribute( 196 """The role of this membership.""") 197 198 moderation_action = Attribute( 199 """The moderation action for this member as an `Action`.""") 200 201 def unsubscribe(): 202 """Unsubscribe (and delete) this member from the mailing list.""" 203 204 acknowledge_posts = Attribute( 205 """Send an acknowledgment for every posting? 206 207 Unlike going through the preferences, this attribute return the 208 preference value based on the following lookup order: 209 210 1. The member 211 2. The address 212 3. The user 213 4. System default 214 """) 215 216 preferred_language = Attribute( 217 """The preferred language for interacting with a mailing list. 218 219 Unlike going through the preferences, this attribute return the 220 preference value based on the following lookup order: 221 222 1. The member 223 2. The address 224 3. The user 225 4. System default 226 """) 227 228 receive_list_copy = Attribute( 229 """Should an explicit recipient receive a list copy? 230 231 Unlike going through `preferences`, this attribute returns the 232 preference value based on the following lookup order: 233 234 1. The member 235 2. The address 236 3. The user 237 4. System default 238 """) 239 240 receive_own_postings = Attribute( 241 """Should the poster get a list copy of their own messages? 242 243 Unlike going through `preferences`, this attribute returns the 244 preference value based on the following lookup order: 245 246 1. The member 247 2. The address 248 3. The user 249 4. System default 250 """) 251 252 delivery_mode = Attribute( 253 """The preferred delivery mode. 254 255 Unlike going through `preferences`, this attribute returns the 256 preference value based on the following lookup order: 257 258 1. The member 259 2. The address 260 3. The user 261 4. System default 262 """) 263 264 delivery_status = Attribute( 265 """The delivery status. 266 267 Unlike going through `preferences`, this attribute returns the 268 preference value based on the following lookup order: 269 270 1. The member 271 2. The address 272 3. The user 273 4. System default 274 275 XXX I'm not sure this is the right place to put this.""") 276 277 bounce_score = Attribute( 278 """The bounce score of this address for the list_id. 279 280 This is a metric of how much the emails sent to this address bounces. 281 This value is incremented first time a bounce is received for the 282 address and mailing list pair on that day. 283 284 The bounce_score of an address on one mailing list does not affect 285 their bounce_score on other mailing lists. 286 """) 287 288 last_bounce_received = Attribute( 289 """The last time when a bounce was received for this mailing list 290 address pair. 291 """) 292 293 last_warnings_sent = Attribute( 294 """The last time when a warning email was sent to the address""") 295 296 total_warnings_sent = Attribute( 297 """Total number of warnings sent to the address. """) 298 299 disabled = Attribute( 300 """Has the email delivery been disabled. """) 301 302 def reset_bounce_info(): 303 """Reset the bounce related information. """ 304 305 306@public 307class IMembershipManager(Interface): 308 """Member object manager.""" 309 310 def memberships_pending_warning(): 311 """Memberships that have been disabled due to excessive bounces and require a 312 warning to be sent. 313 314 The total number of warnings sent to these are less than the 315 MailingList's configured number of warnings to be sent before the 316 membership is removed. 317 """ 318 319 def memberships_pending_removal(): 320 """Memberships pending removal. 321 322 These memberships have maximum number of warnings already sent out and 323 are pending removal. 324 """ 325