Repository: nvaccess/nvda

Issue:
nvda does not automatically read messages received on skype for business

### Steps to reproduce:
I'm sure I've seen this working for some time.
I have not used skype for business for a long time but in my current work I'm making use of it again, and when I received a message on skype for business, nvda did not automatically read the received message.
Open a conversation on skype for business type something and wait inside the conversation for your partner to respond.
### Actual behavior:
The nvda is mute.
### Expected behavior:
Nvda should automatically announce the response received.
### System configuration
#### NVDA installed/portable/running from source:
install
#### NVDA version:
2018.4.1
#### Windows version:
10 17134.556
#### Name and version of other software in use when reproducing the issue:
office 16.0.4266.1001

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your PC?
yes
#### Have you tried any other versions of NVDA?
no


Discussion:
@fernando-jose-silva is this issue also reproducible in NVDA 2019.1.1?
yes, this is still a problem, tested on nvda 2019.1.1 and alpha 17077.
Thank you.
I have just reproduced the issue here with NVDA 2020.4 and Skype for business 2016 (16.0.5095.1000).

### Technical details

When a new message occurs in the conversation, the following error appears in the log and the message is not vocalized:

> ```
> ERROR - eventHandler.executeEvent (14:58:49.118) - MainThread (13480):
> error executing event: liveRegionChange on <appModules.lync.NetUIRicherLabel object at 0x00A49150> with extra args of {}
> Traceback (most recent call last):
> File "eventHandler.pyc", line 246, in executeEvent
> File "eventHandler.pyc", line 96, in __init__
> File "eventHandler.pyc", line 105, in next
> File "appModules\lync.pyc", line 26, in event_liveRegionChange
> AttributeError: 'NoneType' object has no attribute 'replace'
> ```

If I put the navigation object (and the focus) on the last message and open the log, I get the following dev info:

> ```

> INFO - globalCommands.GlobalCommands.script_navigatorObject_devInfo (15:00:46.107) - MainThread (13480):
> Developer info for navigator object:
> name: 'DOE John : , , hello\r\n\r\ngoodbye, , 15:00.'
> role: ROLE_LISTITEM
> roleText: None
> states: STATE_FOCUSABLE, STATE_SELECTABLE, STATE_FOCUSED, STATE_SELECTED
> isFocusable: True
> hasFocus: True
> Python object: <NVDAObjects.Dynamic_ColumnsReview64ListItemUIA object at 0x067E15B0>
> Python class mro: (<class 'NVDAObjects.Dynamic_ColumnsReview64ListItemUIA'>, <class 'globalPlugins.columnsReview.ColumnsReview64'>, <class 'globalPlugins.columnsReview.ColumnsReview'>, <class 'NVDAObjects.behaviors.RowWithFakeNavigation'>, <class 'NVDAObjects.UIA.ListItem'>, <class 'NVDAObjects.UIA.UIA'>, <class 'NVDAObjects.window.Window'>, <class 'NVDAObjects.NVDAObject'>, <class 'documentBase.TextContainerObject'>, <class 'baseObject.ScriptableObject'>, <class 'baseObject.AutoPropertyObject'>, <class 'garbageHandler.TrackedObject'>, <class 'object'>)
> description: ''
> location: RectLTWH(left=101, top=298, width=411, height=82)
> value: None
> appModule: <'lync' (appName 'lync', process ID 12568) at address 886e90>
> appModule.productName: 'Microsoft Office 2016'
> appModule.productVersion: '16.0.5095.1000'
> TextInfo: <class 'NVDAObjects.NVDAObjectTextInfo'>
> windowHandle: 4262932
> windowClassName: 'NetUIHWND'
> windowControlID: 0
> windowStyle: 1442840576
> extendedWindowStyle: 0
> windowThreadID: 9660
> windowText: ''
> displayText: ''
> UIAElement: <POINTER(IUIAutomationElement) ptr=0x818db28 at 751fc60>
> UIA automationID: 
> UIA frameworkID: 
> UIA runtimeID: (42, 4262932, 4, 614633560, 0)
> UIA providerDescription: [pid:12568,providerId:0x0 Main(parent link):Unidentified Provider (unmanaged:mso40uiwin32client.dll)]
> UIA className: NetUIListViewItem
> UIA patterns available: ScrollItemPattern, SelectionItemPattern, LegacyIAccessiblePattern
> ```

Looking at the comments in lync.py appModule that manages this application, it is clear that it was not expected that the object containing the message have a value set to None.
Probably it was different in a previous version of Skype for business / Lync.


Resolving patch:
diff --git a/source/appModules/lync.py b/source/appModules/lync.py
--- a/source/appModules/lync.py
+++ b/source/appModules/lync.py
@@ -4,10 +4,14 @@
 #Copyright (C) 2017 NV Access Limited
 
 """appModule for Microsoft Skype for business. """
- 
+
 import ui
 from NVDAObjects.UIA import UIA
 import appModuleHandler
+from logHandler import log
+
+import re
+
 
 class NetUIRicherLabel(UIA):
 	"""A label sometimes found within list items that can fire live region changes, such as for chat messages."""
@@ -15,19 +19,45 @@ class NetUIRicherLabel(UIA):
 	def event_liveRegionChange(self):
 		# The base liveRegionChange event is not enough as Skype for Business concatinates recent chat messages from the same person within the same minute
 		# Therefore, specifically strip out the chat content and only report the most recent part added.
-		# The object's name contains the full message (I.e. person: content, timestamp) loosely separated by commas.
+		# When not empty, the object's name contains the full message (I.e. person: content, timestamp)
+		# loosely separated by commas.
 		# Example string: "Michael Curran : , , Hello\r\n\r\nThis is a test , 10:45 am."
 		# Where person is "Michael Curran", content is "Hello\nThis is a test" and timestamp is "10:45 am" 
-		# The object's value just contains the content.
-		# Example: "Hello\rThis is a test"
-		# We are only interested in person and content
-		# Therefore use value (content) to  locate and split off the person from the name (fullText)
+		
 		# Normalize the usage of end-of-line characters (name and value seem to expose them differently, which would break comparison)
-		content=self.value.replace('\r','\n').strip()
 		fullText=self.name.replace('\r\n\r\n','\n')
+		
+		# At the object's creation, an unuseful liveRegionChange event is triggered with an empty name,
+		# so we discard it.
+		if not self.name.strip():
+			return
+		
+		if self.value is not None:
+			# For some versions of Lync / Skype for Business, the object's value contains just the content.
+			# Example: "Hello\rThis is a test"
+			# We are only interested in person and content
+			# Therefore use value (content) to  locate and split off the person from the name (fullText)
+			content = self.value.replace('\r', '\n').strip()
+			contentStartIndex = fullText.find(content)
+			pretext = fullText[:contentStartIndex]
+		else:
+			# For other versions of Lync / Skype for Business, self.value is just None.
+			# So we just look at self.name formatting to split content from person and timestamp (less robust).
+			pattern = r'^(?P<name>.+?): (?P<priority>.*?), , (?P<content>.+),(?!, , ) , (?P<timestamp>.+)'
+			match = re.match(pattern, self.name, flags=re.DOTALL)
+			if match:
+				pretext = match['name']
+				priority = match['priority']
+				content = match['content']
+				if priority:
+					content = priority + ', ' + content
+			else:
+				# In case no match is found, log the unexpected message and return the whole message.
+				log.error(f'Unrecognized pattern in the following message: {self.name}')
+				pretext = ''
+				content = self.name
+			content = content.replace('\r', '\n').strip()
 		contentLines=content.split('\n')
-		contentStartIndex=fullText.find(content)
-		pretext=fullText[:contentStartIndex]
 		# There are some annoying comma characters  after the person's name 
 		pretext=pretext.replace(' ,','')
 		# If the objects are the same, the person is the same, and the new content is the old content but with more appended, report the appended content


Test patch: